├── .gitignore ├── backend ├── app │ ├── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── agents │ │ │ ├── __init__.py │ │ │ ├── base │ │ │ │ ├── __init__.py │ │ │ │ ├── template.py │ │ │ │ ├── summary.py │ │ │ │ └── triage.py │ │ │ ├── sheets.py │ │ │ ├── main.py │ │ │ └── x.py │ │ ├── query │ │ │ ├── __init__.py │ │ │ ├── confirm.py │ │ │ └── base.py │ │ ├── user │ │ │ ├── __init__.py │ │ │ └── login.py │ │ ├── integrations │ │ │ ├── __init__.py │ │ │ ├── sheets.py │ │ │ ├── x.py │ │ │ ├── slack.py │ │ │ ├── base.py │ │ │ ├── docs.py │ │ │ ├── gmail.py │ │ │ └── calendar.py │ │ ├── feedback.py │ │ └── token.py │ ├── sandbox │ │ ├── __init__.py │ │ └── integrations │ │ │ ├── __init__.py │ │ │ ├── slack.py │ │ │ ├── x.py │ │ │ ├── g_sheets.py │ │ │ ├── linear.py │ │ │ ├── gmail.py │ │ │ └── g_docs.py │ ├── services │ │ ├── __init__.py │ │ ├── feedback.py │ │ ├── message.py │ │ ├── user.py │ │ └── token.py │ ├── utils │ │ ├── __init__.py │ │ ├── levenshtein.py │ │ └── tools.py │ ├── connectors │ │ ├── __init__.py │ │ ├── native │ │ │ ├── orm.py │ │ │ ├── __init__.py │ │ │ ├── stores │ │ │ │ ├── __init__.py │ │ │ │ ├── base.py │ │ │ │ ├── feedback.py │ │ │ │ ├── user.py │ │ │ │ ├── message.py │ │ │ │ └── token.py │ │ │ └── utils.py │ │ └── client │ │ │ ├── __init__.py │ │ │ ├── x.py │ │ │ ├── slack.py │ │ │ └── sheets.py │ ├── controllers │ │ ├── __init__.py │ │ ├── feedback.py │ │ ├── user.py │ │ ├── query.py │ │ └── token.py │ ├── exceptions │ │ ├── __init__.py │ │ └── exception.py │ ├── config.py │ ├── middleware.py │ └── main.py ├── images │ ├── __init__.py │ ├── supabase_connect.png │ ├── supabase_copy_uri.png │ ├── supabase_create_project.png │ └── supabase_transaction_mode.png ├── .env.example ├── docker │ └── development │ │ └── Dockerfile ├── pyproject.toml ├── .gitignore └── README.md ├── frontend ├── .jest │ ├── setEnvVars.ts │ └── jest.setup.ts ├── src │ ├── app │ │ ├── icon.png │ │ ├── favicon.ico │ │ ├── apple-icon.png │ │ ├── (auth) │ │ │ ├── layout.tsx │ │ │ ├── sign-in │ │ │ │ └── [[...sign-in]] │ │ │ │ │ └── page.tsx │ │ │ └── sign-up │ │ │ │ └── [[...sign-up]] │ │ │ │ └── page.tsx │ │ ├── not-found.tsx │ │ ├── chat │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── api │ │ │ └── oauth2 │ │ │ ├── callback │ │ │ └── route.ts │ │ │ └── login │ │ │ └── route.ts │ ├── components │ │ ├── accessory │ │ │ ├── loader.tsx │ │ │ └── shimmer.tsx │ │ ├── shared │ │ │ ├── theme │ │ │ │ ├── provider.tsx │ │ │ │ └── toggle.tsx │ │ │ ├── query-provider.tsx │ │ │ ├── page-loading-indicator.tsx │ │ │ └── header │ │ │ │ ├── buttons.tsx │ │ │ │ ├── feedback │ │ │ │ ├── button.tsx │ │ │ │ └── form.tsx │ │ │ │ └── navigation.tsx │ │ ├── dialog-content │ │ │ ├── gmail.tsx │ │ │ ├── slack.tsx │ │ │ ├── docs.tsx │ │ │ ├── calendar.tsx │ │ │ ├── sheets.tsx │ │ │ ├── outlook.tsx │ │ │ ├── x.tsx │ │ │ ├── linear.tsx │ │ │ └── routing-base.tsx │ │ ├── home │ │ │ ├── chat │ │ │ │ ├── clear-button.tsx │ │ │ │ └── verification-checkbox.tsx │ │ │ ├── input │ │ │ │ └── verification-option.tsx │ │ │ ├── input-container.tsx │ │ │ └── integration-icon.tsx │ │ ├── ui │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── toaster.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── avatar.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── table.tsx │ │ │ ├── dialog.tsx │ │ │ ├── use-toast.ts │ │ │ └── form.tsx │ │ ├── integration-auth.tsx │ │ └── api-key.tsx │ ├── constants │ │ ├── keys.ts │ │ └── route.ts │ ├── types │ │ ├── actions │ │ │ ├── feedback │ │ │ │ └── form.ts │ │ │ ├── user │ │ │ │ └── login.ts │ │ │ ├── token.ts │ │ │ └── query │ │ │ │ ├── confirm.ts │ │ │ │ └── base.ts │ │ ├── store │ │ │ ├── integrations.ts │ │ │ └── base.ts │ │ ├── api │ │ │ └── token.ts │ │ └── integration.ts │ ├── actions │ │ ├── feedback │ │ │ └── submit.ts │ │ ├── user │ │ │ └── login.ts │ │ ├── token.ts │ │ └── query │ │ │ ├── base.ts │ │ │ └── confirm.ts │ ├── middleware.ts │ ├── styles │ │ └── globals.css │ └── lib │ │ └── utils.ts ├── public │ ├── assistant.png │ └── placeholder.png ├── images │ └── clerk_environment_variables.png ├── postcss.config.mjs ├── .env.example ├── components.json ├── docker │ └── development │ │ └── Dockerfile ├── .gitignore ├── next.config.mjs ├── jest.config.ts ├── tsconfig.json ├── README.md ├── .eslintrc.js ├── package.json └── tailwind.config.ts ├── .env.example ├── docker-compose.yml ├── README.md └── .github └── workflows └── CI.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .env -------------------------------------------------------------------------------- /backend/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/images/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.jest/setEnvVars.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/sandbox/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/connectors/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/connectors/native/orm.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/agents/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/query/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/user/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/connectors/client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/connectors/native/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/agents/base/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/models/integrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/connectors/native/stores/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/sandbox/integrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/config.py: -------------------------------------------------------------------------------- 1 | OPENAI_GPT4O_MINI = "gpt-4o-mini" 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Ensures that the auto-complete python import paths are correct 2 | PYTHONPATH=./backend -------------------------------------------------------------------------------- /frontend/src/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/src/app/icon.png -------------------------------------------------------------------------------- /frontend/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/src/app/favicon.ico -------------------------------------------------------------------------------- /frontend/public/assistant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/public/assistant.png -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # No quotation marks 2 | OPENAI_API_KEY= 3 | DATABASE_URL= 4 | OPENAI_BASE_URL=https://api.openai.com/v1 -------------------------------------------------------------------------------- /frontend/public/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/public/placeholder.png -------------------------------------------------------------------------------- /frontend/src/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/src/app/apple-icon.png -------------------------------------------------------------------------------- /backend/images/supabase_connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/backend/images/supabase_connect.png -------------------------------------------------------------------------------- /backend/images/supabase_copy_uri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/backend/images/supabase_copy_uri.png -------------------------------------------------------------------------------- /backend/images/supabase_create_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/backend/images/supabase_create_project.png -------------------------------------------------------------------------------- /backend/images/supabase_transaction_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/backend/images/supabase_transaction_mode.png -------------------------------------------------------------------------------- /frontend/images/clerk_environment_variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/giga-controller/controller/HEAD/frontend/images/clerk_environment_variables.png -------------------------------------------------------------------------------- /backend/app/models/integrations/sheets.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class SheetsGetRequest(BaseModel): 5 | spreadsheet_id: str 6 | sheet_name: str 7 | -------------------------------------------------------------------------------- /frontend/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 | -------------------------------------------------------------------------------- /backend/app/models/feedback.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class FeedbackRequest(BaseModel): 7 | id: Optional[str] = None 8 | feedback: str 9 | -------------------------------------------------------------------------------- /frontend/src/components/accessory/loader.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2 } from "lucide-react"; 2 | 3 | export default function Loader() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /backend/app/models/integrations/x.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class Tweet(BaseModel): 5 | id: str 6 | text: str 7 | 8 | 9 | class XSendTweetRequest(BaseModel): 10 | text: str 11 | -------------------------------------------------------------------------------- /backend/app/models/user/login.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class LoginRequest(BaseModel): 5 | id: str 6 | name: str 7 | email: str 8 | 9 | 10 | class LoginResponse(BaseModel): 11 | api_key: str 12 | -------------------------------------------------------------------------------- /frontend/src/constants/keys.ts: -------------------------------------------------------------------------------- 1 | export const API_KEY_QUERY_KEY = "apiKey"; 2 | export const CHAT_HISTORY_QUERY_KEY = "chatHistoryKey"; 3 | export const INTEGRATION_AUTH_STATUS_QUERY_KEY = (name: string) => { 4 | return `is${name}Authenticated`; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/.env.example: -------------------------------------------------------------------------------- 1 | # No quotation marks 2 | NEXT_PUBLIC_DEFAULT_SITE_URL=http://localhost:3000 3 | NEXT_PUBLIC_BACKEND_URL=http://0.0.0.0:8080 4 | NEXT_PUBLIC_DOCS_URL=https://controller-docs.vercel.app/ 5 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= 6 | CLERK_SECRET_KEY= -------------------------------------------------------------------------------- /frontend/src/app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
{children}
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /backend/app/models/integrations/slack.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class SlackGetChannelIdRequest(BaseModel): 5 | channel_names: list[str] 6 | 7 | 8 | class SlackSendMessageRequest(BaseModel): 9 | channel_id: str 10 | text: str 11 | -------------------------------------------------------------------------------- /frontend/src/types/actions/feedback/form.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const feedbackRequestSchema = z.object({ 4 | id: z.string().nullable(), 5 | feedback: z.string().min(1, "Feedback is required"), 6 | }); 7 | 8 | export type FeedbackRequest = z.infer; 9 | -------------------------------------------------------------------------------- /frontend/src/components/shared/theme/provider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 3 | import { type ThemeProviderProps } from "next-themes/dist/types"; 4 | 5 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 6 | return {children}; 7 | } 8 | -------------------------------------------------------------------------------- /backend/app/models/integrations/base.py: -------------------------------------------------------------------------------- 1 | from enum import StrEnum 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Integration(StrEnum): 7 | GMAIL = "gmail" 8 | CALENDAR = "calendar" 9 | DOCS = "docs" 10 | LINEAR = "linear" 11 | SLACK = "slack" 12 | X = "x" 13 | NONE = "none" 14 | 15 | 16 | class SummaryResponse(BaseModel): 17 | summary: str 18 | -------------------------------------------------------------------------------- /frontend/src/components/accessory/shimmer.tsx: -------------------------------------------------------------------------------- 1 | import Loader from "@/components/accessory/loader"; 2 | 3 | export default function Shimmer() { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.jest/jest.setup.ts: -------------------------------------------------------------------------------- 1 | import "@testing-library/jest-dom"; 2 | import fetchMock from "jest-fetch-mock"; 3 | 4 | // Mock ResizeObserver 5 | class ResizeObserver { 6 | observe() { 7 | // do nothing 8 | } 9 | unobserve() { 10 | // do nothing 11 | } 12 | disconnect() { 13 | // do nothing 14 | } 15 | } 16 | 17 | global.ResizeObserver = ResizeObserver; 18 | fetchMock.enableMocks(); 19 | -------------------------------------------------------------------------------- /frontend/src/components/shared/query-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 3 | import { ReactNode } from "react"; 4 | 5 | export const queryClient = new QueryClient(); 6 | 7 | export function QueryProvider({ children }: { children: ReactNode }) { 8 | return ( 9 | {children} 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /frontend/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": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/types/actions/user/login.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const loginRequestSchema = z.object({ 4 | id: z.string(), 5 | name: z.string(), 6 | email: z.string(), 7 | }); 8 | 9 | export type LoginRequest = z.infer; 10 | 11 | export const loginResponseSchema = z.object({ 12 | api_key: z.string(), 13 | }); 14 | 15 | export type LoginResponse = z.infer; 16 | -------------------------------------------------------------------------------- /frontend/src/types/actions/token.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const tokenGetRequestSchema = z.object({ 4 | api_key: z.string(), 5 | table_name: z.string(), 6 | }); 7 | 8 | export type TokenGetRequest = z.infer; 9 | 10 | export const tokenGetResponseSchema = z.object({ 11 | is_authenticated: z.boolean(), 12 | }); 13 | 14 | export type TokenGetResponse = z.infer; 15 | -------------------------------------------------------------------------------- /frontend/src/types/store/integrations.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defaultIntegrationsState, 3 | integrationsStateSchema, 4 | } from "@/types/integration"; 5 | import { createPersistedStore } from "@/types/store/base"; 6 | 7 | export const useIntegrationsStore = createPersistedStore( 8 | "integrations", 9 | defaultIntegrationsState, 10 | integrationsStateSchema, 11 | "integrationsState", 12 | "setIntegrationsState", 13 | "resetIntegrationsState", 14 | ); 15 | -------------------------------------------------------------------------------- /backend/app/models/query/confirm.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from app.models.integrations.base import Integration 6 | from app.models.query.base import Message 7 | 8 | 9 | class ConfirmRequest(BaseModel): 10 | chat_history: list[Message] 11 | api_key: str 12 | enable_verification: bool 13 | integrations: list[Integration] 14 | function_to_verify: str 15 | instance: Optional[str] = None 16 | -------------------------------------------------------------------------------- /backend/app/services/feedback.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from app.connectors.native.stores.feedback import Feedback, FeedbackORM 4 | from app.connectors.orm import Orm 5 | 6 | orm = Orm() 7 | 8 | 9 | class FeedbackService: 10 | async def post(self, id: Optional[str], feedback: str): 11 | await orm.post( 12 | orm_model=FeedbackORM, 13 | data=[Feedback.local(user_id=id, feedback=feedback).model_dump()], 14 | ) 15 | -------------------------------------------------------------------------------- /backend/app/models/token.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class TokenPostRequest(BaseModel): 7 | api_key: str 8 | access_token: str 9 | refresh_token: Optional[str] 10 | client_id: str 11 | client_secret: str 12 | table_name: str 13 | 14 | 15 | class TokenGetRequest(BaseModel): 16 | api_key: str 17 | table_name: str 18 | 19 | 20 | class TokenGetResponse(BaseModel): 21 | is_authenticated: bool 22 | -------------------------------------------------------------------------------- /frontend/docker/development/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | RUN apk add --no-cache libc6-compat 4 | 5 | WORKDIR /app 6 | 7 | COPY package.json ./ 8 | COPY package-lock.json ./ 9 | COPY .env ./ 10 | 11 | RUN npm ci 12 | 13 | COPY *.ts ./ 14 | COPY *.mjs ./ 15 | COPY *.js ./ 16 | COPY tsconfig.json ./ 17 | COPY components.json ./ 18 | COPY src ./src 19 | COPY public ./public 20 | 21 | ENV NODE_ENV=development 22 | 23 | EXPOSE 3000 24 | 25 | CMD ["npm", "run", "dev"] 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { SignIn } from "@clerk/nextjs"; 3 | import { useTheme } from "next-themes"; 4 | import { dark } from "@clerk/themes"; 5 | 6 | export default function SignInPage() { 7 | const { resolvedTheme } = useTheme(); 8 | 9 | return ( 10 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/(auth)/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { dark } from "@clerk/themes"; 4 | import { useTheme } from "next-themes"; 5 | import { SignUp } from "@clerk/nextjs"; 6 | 7 | export default function SignUpPage() { 8 | const { resolvedTheme } = useTheme(); 9 | 10 | return ( 11 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/actions/feedback/submit.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { FeedbackRequest } from "@/types/actions/feedback/form"; 4 | 5 | import axios from "axios"; 6 | 7 | const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL; 8 | const SERVICE_ENDPOINT = "api/feedback"; 9 | 10 | export async function submitFeedback(input: FeedbackRequest) { 11 | try { 12 | await axios.post(`${BACKEND_URL}/${SERVICE_ENDPOINT}`, input); 13 | } catch (error) { 14 | console.error(error); 15 | throw error; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/types/api/token.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const tokenPostRequestSchema = z.object({ 4 | api_key: z.string(), 5 | access_token: z.string(), 6 | refresh_token: z.string().nullable(), 7 | client_id: z.string(), 8 | client_secret: z.string(), 9 | table_name: z.string(), 10 | }); 11 | 12 | export type TokenPostRequest = z.infer; 13 | 14 | export const requestTypeSchema = z.enum(["GET", "POST"]); 15 | 16 | export type RequestType = z.infer; 17 | -------------------------------------------------------------------------------- /frontend/src/components/shared/page-loading-indicator.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { Loader2 } from "lucide-react"; 3 | 4 | export default function PageLoader({ className }: { className?: string }) { 5 | return ( 6 |
12 | 13 |
Loading...
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/.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 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /backend/docker/development/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.1-slim 2 | 3 | # Set working directory 4 | WORKDIR /app 5 | 6 | # Install Poetry 7 | RUN pip install --no-cache-dir poetry 8 | 9 | # Copy pyproject.toml and poetry.lock 10 | COPY pyproject.toml poetry.lock ./ 11 | 12 | # Install dependencies 13 | RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi 14 | 15 | # Copy the rest of the code 16 | COPY . . 17 | 18 | # Expose port 8080 19 | EXPOSE 8080 20 | 21 | # Start Uvicorn server 22 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"] 23 | -------------------------------------------------------------------------------- /backend/app/connectors/native/stores/base.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class BaseObject(BaseModel): 7 | 8 | class Config: 9 | orm_mode = True 10 | from_attributes = True 11 | 12 | @staticmethod 13 | def generate_id( 14 | **kwargs, 15 | ) -> str: 16 | for k, v in kwargs.items(): 17 | if v is None: 18 | raise Exception(f"Cannot generate id with None value for key {k}") 19 | 20 | return str( 21 | uuid.uuid3(uuid.NAMESPACE_DNS, "-".join([str(v) for v in kwargs.values()])) 22 | ) 23 | -------------------------------------------------------------------------------- /frontend/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | env: { 4 | NEXT_PUBLIC_CLERK_SIGN_IN_URL: "/sign-in", 5 | NEXT_PUBLIC_CLERK_SIGN_UP_URL: "/sign-up", 6 | NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL: "/", 7 | NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL: "/", 8 | }, 9 | async headers() { 10 | return [ 11 | { 12 | source: "/(.*)", 13 | headers: [ 14 | { 15 | key: "Content-Security-Policy", 16 | value: "frame-ancestors 'self'", 17 | }, 18 | ], 19 | }, 20 | ]; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/types/actions/query/confirm.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { integrationEnum } from "@/types/integration"; 3 | import { messageSchema } from "@/types/actions/query/base"; 4 | 5 | export const confirmRequestSchema = z.object({ 6 | chat_history: z.array(messageSchema), 7 | api_key: z.string(), 8 | enable_verification: z.boolean(), 9 | integrations: z.array(integrationEnum), 10 | function_to_verify: z.string(), 11 | instance: z.string().nullable(), 12 | }); 13 | 14 | export type ConfirmRequest = z.infer; 15 | 16 | export const userVerificationSchema = z.enum(["YES", "NO"]); 17 | -------------------------------------------------------------------------------- /frontend/jest.config.ts: -------------------------------------------------------------------------------- 1 | import nextJest from "next/jest.js"; 2 | 3 | import type { Config } from "jest"; 4 | 5 | const createJestConfig = nextJest({ 6 | dir: "./", 7 | }); 8 | 9 | const config: Config = { 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: "coverage", 13 | coverageProvider: "v8", 14 | verbose: true, 15 | preset: "ts-jest", 16 | testEnvironment: "jest-environment-jsdom", 17 | testPathIgnorePatterns: ["/node_modules/", "/e2e/"], 18 | setupFiles: ["/.jest/setEnvVars.ts"], 19 | setupFilesAfterEnv: ["/.jest/jest.setup.ts"], 20 | }; 21 | 22 | export default createJestConfig(config); 23 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/gmail.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function GmailAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/slack.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function SlackAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/docs.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function GoogleDocsAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/calendar.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function GoogleCalendarAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/sheets.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function GoogleSheetsAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/outlook.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function MicrosoftOutlookAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/components/dialog-content/x.tsx: -------------------------------------------------------------------------------- 1 | import { AuthParamProps, integrationEnum } from "@/types/integration"; 2 | import AuthDialogContent from "@/components/dialog-content/auth-base"; 3 | import { capitaliseFirstLetter } from "@/lib/utils"; 4 | 5 | export default function XAuthDialogContent({ 6 | apiKey, 7 | loginBase, 8 | exchangeBase, 9 | }: AuthParamProps) { 10 | return ( 11 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | networks: 4 | controller_network: 5 | driver: bridge 6 | 7 | services: 8 | backend: 9 | build: 10 | context: ./backend 11 | dockerfile: docker/development/Dockerfile 12 | networks: 13 | - controller_network 14 | container_name: backend_container 15 | ports: 16 | - "8080:8080" 17 | 18 | frontend: 19 | build: 20 | context: ./frontend 21 | dockerfile: docker/development/Dockerfile 22 | networks: 23 | - controller_network 24 | container_name: frontend_container 25 | ports: 26 | - "3000:3000" 27 | environment: 28 | - NEXT_PUBLIC_BACKEND_URL=http://backend_container:8080 29 | -------------------------------------------------------------------------------- /frontend/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 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/components/home/chat/clear-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Message } from "@/types/actions/query/base"; 3 | 4 | type ClearButtonProps = { 5 | updateChatHistory: (newChatHistory: Message[]) => void; 6 | updateInstance: (instance: string | null) => void; 7 | }; 8 | 9 | export default function ClearButton({ 10 | updateChatHistory, 11 | updateInstance, 12 | }: ClearButtonProps) { 13 | return ( 14 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/actions/user/login.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { 4 | LoginRequest, 5 | loginResponseSchema, 6 | LoginResponse, 7 | } from "@/types/actions/user/login"; 8 | 9 | import axios from "axios"; 10 | 11 | const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL; 12 | const SERVICE_ENDPOINT = "api/user/login"; 13 | 14 | export async function login(input: LoginRequest): Promise { 15 | try { 16 | const response = await axios.post( 17 | `${BACKEND_URL}/${SERVICE_ENDPOINT}`, 18 | input, 19 | ); 20 | const loginResponse = loginResponseSchema.parse(response.data); 21 | 22 | return loginResponse; 23 | } catch (error) { 24 | console.error(error); 25 | throw error; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/actions/token.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { 4 | TokenGetRequest, 5 | tokenGetResponseSchema, 6 | TokenGetResponse, 7 | } from "@/types/actions/token"; 8 | 9 | import axios from "axios"; 10 | 11 | const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL; 12 | const SERVICE_ENDPOINT = "api/token"; 13 | 14 | export async function isUserAuthenticated( 15 | input: TokenGetRequest, 16 | ): Promise { 17 | try { 18 | const response = await axios.get(`${BACKEND_URL}/${SERVICE_ENDPOINT}`, { 19 | params: input, 20 | }); 21 | const tokenResponse = tokenGetResponseSchema.parse(response.data); 22 | 23 | return tokenResponse; 24 | } catch (error) { 25 | console.error(error); 26 | throw error; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/app/models/agents/sheets.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from app.config import OPENAI_GPT4O_MINI 4 | from app.models.agents.base.summary import transfer_to_summary_agent 5 | from app.models.agents.base.triage import TriageAgent 6 | 7 | logging.basicConfig(level=logging.INFO) 8 | logging.getLogger("httpx").setLevel(logging.WARNING) 9 | 10 | log = logging.getLogger(__name__) 11 | 12 | SHEETS_TRIAGE_AGENT = TriageAgent( 13 | name="Google Sheets Triage Agent", 14 | model=OPENAI_GPT4O_MINI, 15 | system_prompt="You are an expert at choosing the right agent to perform the task described by the user. When you deem that the task is completed or cannot be completed, pass the task to the summary agent.", 16 | tools=[ 17 | transfer_to_summary_agent, 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /frontend/src/actions/query/base.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { 4 | QueryRequest, 5 | queryResponseSchema, 6 | QueryResponse, 7 | } from "@/types/actions/query/base"; 8 | 9 | import axios from "axios"; 10 | 11 | const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL; 12 | const SERVICE_ENDPOINT = "api/query"; 13 | 14 | export async function query(input: QueryRequest): Promise { 15 | try { 16 | const response = await axios.post( 17 | `${BACKEND_URL}/${SERVICE_ENDPOINT}`, 18 | input, 19 | { timeout: 40000 }, 20 | ); 21 | const queryResponse = queryResponseSchema.parse(response.data); 22 | 23 | return queryResponse; 24 | } catch (error) { 25 | console.error("Error in query endpoint"); 26 | throw error; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/shared/theme/toggle.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { Button } from "@/components/ui/button"; 3 | import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; 4 | 5 | export function ThemeToggle() { 6 | const { theme, setTheme } = useTheme(); 7 | 8 | return ( 9 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /backend/app/middleware.py: -------------------------------------------------------------------------------- 1 | from starlette.exceptions import HTTPException 2 | from starlette.middleware.base import BaseHTTPMiddleware 3 | from starlette.requests import Request 4 | from starlette.responses import Response 5 | 6 | 7 | class LimitRequestSizeMiddleware(BaseHTTPMiddleware): 8 | def __init__(self, app, max_body_size: int): 9 | super().__init__(app) 10 | self.max_body_size = max_body_size 11 | 12 | async def dispatch(self, request: Request, call_next): 13 | if request.headers.get("content-length"): 14 | content_length = int(request.headers["content-length"]) 15 | if content_length > self.max_body_size: 16 | raise HTTPException(status_code=413, detail="Request body too large") 17 | return await call_next(request) 18 | -------------------------------------------------------------------------------- /frontend/src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { ROUTE } from "@/constants/route"; 5 | 6 | import "@/styles/globals.css"; 7 | 8 | export default function NotFound() { 9 | return ( 10 |
11 |
12 |

13 | Oops! Page not found. We are testing in production :( 14 |

15 |

16 | Maybe return to the{" "} 17 | 21 | home page 22 | 23 | ? 24 |

25 |
26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /backend/app/models/query/base.py: -------------------------------------------------------------------------------- 1 | from enum import StrEnum 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from app.models.integrations.base import Integration 7 | 8 | 9 | class Role(StrEnum): 10 | USER = "user" 11 | ASSISTANT = "assistant" 12 | 13 | 14 | class Message(BaseModel): 15 | role: Role 16 | content: str 17 | data: Optional[list[dict]] = None 18 | error: Optional[bool] = False 19 | 20 | 21 | class QueryRequest(BaseModel): 22 | message: Message 23 | chat_history: list[Message] 24 | api_key: str 25 | enable_verification: bool 26 | integrations: list[Integration] 27 | instance: Optional[str] = None 28 | 29 | 30 | class QueryResponse(BaseModel): 31 | chat_history: list[Message] 32 | instance: Optional[str] 33 | function_to_verify: Optional[str] 34 | -------------------------------------------------------------------------------- /frontend/src/actions/query/confirm.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { ConfirmRequest } from "@/types/actions/query/confirm"; 4 | import { QueryResponse, queryResponseSchema } from "@/types/actions/query/base"; 5 | 6 | import axios from "axios"; 7 | 8 | const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL; 9 | const SERVICE_ENDPOINT = "api/query/confirm"; 10 | 11 | export async function confirmExecution( 12 | input: ConfirmRequest, 13 | ): Promise { 14 | try { 15 | const response = await axios.post( 16 | `${BACKEND_URL}/${SERVICE_ENDPOINT}`, 17 | input, 18 | { timeout: 40000 }, 19 | ); 20 | const queryResponse = queryResponseSchema.parse(response.data); 21 | 22 | return queryResponse; 23 | } catch (error) { 24 | console.error("Error in query endpoint"); 25 | throw error; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as LabelPrimitive from "@radix-ui/react-label"; 5 | import { cva, type VariantProps } from "class-variance-authority"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 11 | ); 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )); 24 | Label.displayName = LabelPrimitive.Root.displayName; 25 | 26 | export { Label }; 27 | -------------------------------------------------------------------------------- /frontend/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |