Update feature flags + env docs

This commit is contained in:
ramnique 2025-03-10 12:03:06 +05:30
parent 0df92e80c6
commit cbac042003
20 changed files with 210 additions and 168 deletions

View file

@ -1,9 +1,31 @@
# Basic configuration
# ------------------------------------------------------------
MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:27017/rowboat
OPENAI_API_KEY=<OPENAI_API_KEY> OPENAI_API_KEY=<OPENAI_API_KEY>
MONGODB_CONNECTION_STRING=<MONGODB_CONNECTION_STRING>
AUTH0_SECRET=<AUTH0_SECRET> AUTH0_SECRET=<AUTH0_SECRET>
AUTH0_BASE_URL=http://localhost:3000 AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=<AUTH0_ISSUER_BASE_URL> AUTH0_ISSUER_BASE_URL=<AUTH0_ISSUER_BASE_URL>
AUTH0_CLIENT_ID=<AUTH0_CLIENT_ID> AUTH0_CLIENT_ID=<AUTH0_CLIENT_ID>
AUTH0_CLIENT_SECRET=<AUTH0_CLIENT_SECRET> AUTH0_CLIENT_SECRET=<AUTH0_CLIENT_SECRET>
COPILOT_API_KEY=test
AGENTS_API_KEY=test # Uncomment to enable RAG:
# ------------------------------------------------------------
# USE_RAG=true
# Uncomment to enable RAG: File uploads
# ------------------------------------------------------------
# USE_RAG_UPLOADS=true
# AWS_ACCESS_KEY_ID=<AWS_ACCESS_KEY_ID>
# AWS_SECRET_ACCESS_KEY=<AWS_SECRET_ACCESS_KEY>
# RAG_UPLOADS_S3_BUCKET=<RAG_UPLOADS_S3_BUCKET>
# RAG_UPLOADS_S3_REGION=<RAG_UPLOADS_S3_REGION>
# Uncomment to enable RAG: Scraping URLs
# ------------------------------------------------------------
# USE_RAG_SCRAPING=true
# FIRECRAWL_API_KEY=<FIRECRAWL_API_KEY>
# Uncomment to enable chat widget
# ------------------------------------------------------------
# USE_CHAT_WIDGET=true
# CHAT_WIDGET_SESSION_JWT_SECRET=<CHAT_WIDGET_SESSION_JWT_SECRET>

View file

@ -294,7 +294,7 @@ export async function getDownloadUrlForFile(
} }
const command = new GetObjectCommand({ const command = new GetObjectCommand({
Bucket: process.env.UPLOADS_S3_BUCKET, Bucket: process.env.RAG_UPLOADS_S3_BUCKET,
Key: file.data.s3Key, Key: file.data.s3Key,
}); });
@ -328,7 +328,7 @@ export async function getUploadUrlsForFilesDataSource(
const s3Key = `datasources/files/${projectIdPrefix}/${projectId}/${sourceId}/${fileId}/${file.name}`; const s3Key = `datasources/files/${projectIdPrefix}/${projectId}/${sourceId}/${fileId}/${file.name}`;
// Generate presigned URL // Generate presigned URL
const command = new PutObjectCommand({ const command = new PutObjectCommand({
Bucket: process.env.UPLOADS_S3_BUCKET, Bucket: process.env.RAG_UPLOADS_S3_BUCKET,
Key: s3Key, Key: s3Key,
ContentType: file.type, ContentType: file.type,
}); });

View file

@ -53,6 +53,7 @@ export async function createProject(formData: FormData) {
secret, secret,
nextWorkflowNumber: 1, nextWorkflowNumber: 1,
testRunCounter: 0, testRunCounter: 0,
webhookUrl: 'http://tools_webhook:3005/tool_call',
}); });
// add first workflow version // add first workflow version

View file

@ -0,0 +1,4 @@
export const USE_RAG = process.env.USE_RAG === 'true';
export const USE_RAG_UPLOADS = process.env.USE_RAG_UPLOADS === 'true';
export const USE_RAG_SCRAPING = process.env.USE_RAG_SCRAPING === 'true';
export const USE_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true';

View file

@ -569,9 +569,11 @@ export function DeleteProjectSection({
export default function App({ export default function App({
projectId, projectId,
useChatWidget,
chatWidgetHost, chatWidgetHost,
}: { }: {
projectId: string; projectId: string;
useChatWidget: boolean;
chatWidgetHost: string; chatWidgetHost: string;
}) { }) {
return <div className="flex flex-col h-full"> return <div className="flex flex-col h-full">
@ -586,7 +588,7 @@ export default function App({
<SecretSection projectId={projectId} /> <SecretSection projectId={projectId} />
<ApiKeysSection projectId={projectId} /> <ApiKeysSection projectId={projectId} />
<WebhookUrlSection projectId={projectId} /> <WebhookUrlSection projectId={projectId} />
<ChatWidgetSection projectId={projectId} chatWidgetHost={chatWidgetHost} /> {useChatWidget && <ChatWidgetSection projectId={projectId} chatWidgetHost={chatWidgetHost} />}
<DeleteProjectSection projectId={projectId} /> <DeleteProjectSection projectId={projectId} />
</div> </div>
</div> </div>

View file

@ -1,5 +1,7 @@
import { Metadata } from "next"; import { Metadata } from "next";
import App from "./app"; import App from "./app";
import { USE_CHAT_WIDGET } from "@/app/lib/feature_flags";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Project config", title: "Project config",
}; };
@ -13,6 +15,7 @@ export default function Page({
}) { }) {
return <App return <App
projectId={params.projectId} projectId={params.projectId}
chatWidgetHost={process.env.CHAT_WIDGET_HOST || 'https://chat.rowboatlabs.com'} useChatWidget={USE_CHAT_WIDGET}
chatWidgetHost={process.env.CHAT_WIDGET_HOST || ''}
/>; />;
} }

View file

@ -1,4 +1,5 @@
import { Nav } from "./nav"; import { Nav } from "./nav";
import { USE_RAG } from "@/app/lib/feature_flags";
export default async function Layout({ export default async function Layout({
params, params,
@ -7,10 +8,10 @@ export default async function Layout({
params: { projectId: string } params: { projectId: string }
children: React.ReactNode children: React.ReactNode
}) { }) {
const useDataSources = process.env.USE_DATA_SOURCES === 'true'; const useRag = USE_RAG;
return <div className="flex h-full"> return <div className="flex h-full">
<Nav projectId={params.projectId} useDataSources={useDataSources} /> <Nav projectId={params.projectId} useRag={useRag} />
<div className="grow p-2 overflow-auto bg-background dark:bg-background rounded-tl-lg"> <div className="grow p-2 overflow-auto bg-background dark:bg-background rounded-tl-lg">
{children} {children}
</div> </div>

View file

@ -44,11 +44,11 @@ function NavLink({ href, label, icon, collapsed, selected = false }: {
export default function Menu({ export default function Menu({
projectId, projectId,
collapsed, collapsed,
useDataSources, useRag,
}: { }: {
projectId: string; projectId: string;
collapsed: boolean; collapsed: boolean;
useDataSources: boolean; useRag: boolean;
}) { }) {
const pathname = usePathname(); const pathname = usePathname();
@ -68,7 +68,7 @@ export default function Menu({
icon={<PlayIcon size={16} />} icon={<PlayIcon size={16} />}
selected={pathname.startsWith(`/projects/${projectId}/test`)} selected={pathname.startsWith(`/projects/${projectId}/test`)}
/> />
{useDataSources && ( {useRag && (
<NavLink <NavLink
href={`/projects/${projectId}/sources`} href={`/projects/${projectId}/sources`}
label="Connect" label="Connect"

View file

@ -5,14 +5,14 @@ import { useEffect, useState } from "react";
import clsx from "clsx"; import clsx from "clsx";
import Menu from "./menu"; import Menu from "./menu";
import { getProjectConfig } from "../../actions/project_actions"; import { getProjectConfig } from "../../actions/project_actions";
import { ChevronsLeftIcon, ChevronsRightIcon, FolderOpenIcon, PanelLeftCloseIcon, PanelLeftOpenIcon } from "lucide-react"; import { FolderOpenIcon, PanelLeftCloseIcon, PanelLeftOpenIcon } from "lucide-react";
export function Nav({ export function Nav({
projectId, projectId,
useDataSources, useRag,
}: { }: {
projectId: string; projectId: string;
useDataSources: boolean; useRag: boolean;
}) { }) {
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [projectName, setProjectName] = useState<string | null>(null); const [projectName, setProjectName] = useState<string | null>(null);
@ -56,6 +56,6 @@ export function Nav({
<FolderOpenIcon size={16} className="ml-1" /> <FolderOpenIcon size={16} className="ml-1" />
</Link> </Link>
</Tooltip>} </Tooltip>}
<Menu projectId={projectId} collapsed={collapsed} useDataSources={useDataSources} /> <Menu projectId={projectId} collapsed={collapsed} useRag={useRag} />
</div>; </div>;
} }

View file

@ -186,26 +186,28 @@ function ToolCall({
sender={sender} sender={sender}
/>; />;
} }
if (matchingWorkflowTool && testProfile && !testProfile.mockTools) { if (!matchingWorkflowTool ||
return <ClientToolCall matchingWorkflowTool.mockTool ||
(testProfile && testProfile.mockTools)) {
return <MockToolCall
toolCall={toolCall} toolCall={toolCall}
result={result} result={result}
handleResult={handleResult} handleResult={handleResult}
projectId={projectId} projectId={projectId}
messages={messages} messages={messages}
sender={sender} sender={sender}
testProfile={testProfile}
workflowTool={matchingWorkflowTool}
systemMessage={systemMessage}
/>; />;
} }
return <MockToolCall return <ClientToolCall
toolCall={toolCall} toolCall={toolCall}
result={result} result={result}
handleResult={handleResult} handleResult={handleResult}
projectId={projectId} projectId={projectId}
messages={messages} messages={messages}
sender={sender} sender={sender}
testProfile={testProfile}
workflowTool={matchingWorkflowTool}
systemMessage={systemMessage}
/>; />;
} }
} }

View file

@ -8,27 +8,17 @@ import { PlusIcon } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export function Form({ export function Form({
projectId projectId,
useRagUploads,
useRagScraping,
}: { }: {
projectId: string; projectId: string;
useRagUploads: boolean;
useRagScraping: boolean;
}) { }) {
const [sourceType, setSourceType] = useState(""); const [sourceType, setSourceType] = useState("");
const router = useRouter(); const router = useRouter();
// async function createCrawlDataSource(formData: FormData) {
// const source = await createDataSource({
// projectId,
// name: formData.get('name') as string,
// data: {
// type: 'crawl',
// startUrl: formData.get('startUrl') as string,
// limit: parseInt(formData.get('limit') as string),
// },
// status: 'queued',
// });
// router.push(`/projects/${projectId}/sources/${source._id}`);
// }
async function createUrlsDataSource(formData: FormData) { async function createUrlsDataSource(formData: FormData) {
const source = await createDataSource({ const source = await createDataSource({
projectId, projectId,
@ -80,14 +70,11 @@ export function Form({
label="Select type" label="Select type"
selectedKeys={[sourceType]} selectedKeys={[sourceType]}
onChange={handleSourceTypeChange} onChange={handleSourceTypeChange}
disabledKeys={[
...(useRagUploads ? [] : ['files']),
...(useRagScraping ? [] : ['urls']),
]}
> >
{/* <SelectItem
key="crawl"
value="crawl"
startContent={<DataSourceIcon type="crawl" />}
>
Crawl URLs
</SelectItem> */}
<SelectItem <SelectItem
key="urls" key="urls"
startContent={<DataSourceIcon type="urls" />} startContent={<DataSourceIcon type="urls" />}
@ -102,62 +89,6 @@ export function Form({
</SelectItem> </SelectItem>
</Select> </Select>
{/* {sourceType === "crawl" && <form
action={createCrawlDataSourceWithProjectId}
className="flex flex-col gap-4"
>
<Input
required
type="text"
name="url"
label="Specify starting URL to crawl"
labelPlacement="outside"
placeholder="https://example.com"
variant="bordered"
/>
<div className="self-start w-[200px]">
<Input
required
type="number"
min={1}
max={5000}
name="limit"
label="Maximum pages to crawl"
labelPlacement="outside"
placeholder="100"
defaultValue={"100"}
variant="bordered"
/>
</div>
<div className="self-start">
<Input
required
type="text"
name="name"
label="Name this data source"
labelPlacement="outside"
placeholder="e.g. Help articles"
variant="bordered"
/>
</div>
<div className="text-sm">
<p>Note:</p>
<ul className="list-disc ml-4">
<li>Expect about 5-10 minutes to crawl 100 pages</li>
</ul>
</div>
<FormStatusButton
props={{
type: "submit",
children: "Add data source",
className: "self-start",
startContent: <svg className="w-[24px] h-[24px]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1" d="M5 12h14m-7 7V5" />
</svg>,
}}
/>
</form>} */}
{sourceType === "urls" && <form {sourceType === "urls" && <form
action={createUrlsDataSource} action={createUrlsDataSource}
className="flex flex-col gap-4" className="flex flex-col gap-4"

View file

@ -1,6 +1,7 @@
import { Metadata } from "next"; import { Metadata } from "next";
import { Form } from "./form"; import { Form } from "./form";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { USE_RAG, USE_RAG_UPLOADS, USE_RAG_SCRAPING } from "../../../../lib/feature_flags";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Add data source" title: "Add data source"
@ -11,9 +12,7 @@ export default async function Page({
}: { }: {
params: { projectId: string } params: { projectId: string }
}) { }) {
const useDataSources = process.env.USE_DATA_SOURCES === 'true'; if (!USE_RAG) {
if (!useDataSources) {
redirect(`/projects/${params.projectId}`); redirect(`/projects/${params.projectId}`);
} }
@ -23,6 +22,10 @@ export default async function Page({
<h1 className="text-lg">Add data source</h1> <h1 className="text-lg">Add data source</h1>
</div> </div>
</div> </div>
<Form projectId={params.projectId} /> <Form
projectId={params.projectId}
useRagUploads={USE_RAG_UPLOADS}
useRagScraping={USE_RAG_SCRAPING}
/>
</div>; </div>;
} }

View file

@ -10,8 +10,7 @@ import { ActionButton, StructuredPanel } from "../../../lib/components/structure
import { FormSection } from "../../../lib/components/form-section"; import { FormSection } from "../../../lib/components/form-section";
import { EditableField } from "../../../lib/components/editable-field"; import { EditableField } from "../../../lib/components/editable-field";
import { Label } from "../../../lib/components/label"; import { Label } from "../../../lib/components/label";
import { PlusIcon, SparklesIcon, ChevronRight, ChevronDown } from "lucide-react"; import { PlusIcon, ChevronRight, ChevronDown } from "lucide-react";
import { List } from "./config_list";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { usePreviewModal } from "./preview-modal"; import { usePreviewModal } from "./preview-modal";
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react"; import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter } from "@heroui/react";
@ -33,6 +32,7 @@ export function AgentConfig({
dataSources, dataSources,
handleUpdate, handleUpdate,
handleClose, handleClose,
useRag,
}: { }: {
projectId: string, projectId: string,
workflow: z.infer<typeof Workflow>, workflow: z.infer<typeof Workflow>,
@ -44,6 +44,7 @@ export function AgentConfig({
dataSources: WithStringId<z.infer<typeof DataSource>>[], dataSources: WithStringId<z.infer<typeof DataSource>>[],
handleUpdate: (agent: z.infer<typeof WorkflowAgent>) => void, handleUpdate: (agent: z.infer<typeof WorkflowAgent>) => void,
handleClose: () => void, handleClose: () => void,
useRag: boolean,
}) { }) {
const [isAdvancedConfigOpen, setIsAdvancedConfigOpen] = useState(false); const [isAdvancedConfigOpen, setIsAdvancedConfigOpen] = useState(false);
@ -160,7 +161,7 @@ export function AgentConfig({
/> />
</FormSection> </FormSection>
<FormSection label="RAG (beta)" showDivider> {useRag && <FormSection label="RAG (beta)" showDivider>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<Dropdown> <Dropdown>
<DropdownTrigger> <DropdownTrigger>
@ -272,7 +273,7 @@ export function AgentConfig({
</> </>
)} )}
</div> </div>
</FormSection> </FormSection>}
<FormSection label="Model" showDivider> <FormSection label="Model" showDivider>
<CustomDropdown <CustomDropdown

View file

@ -9,12 +9,13 @@ import { WorkflowSelector } from "./workflow_selector";
import { Spinner } from "@heroui/react"; import { Spinner } from "@heroui/react";
import { cloneWorkflow, createWorkflow, fetchPublishedWorkflowId, fetchWorkflow } from "../../../actions/workflow_actions"; import { cloneWorkflow, createWorkflow, fetchPublishedWorkflowId, fetchWorkflow } from "../../../actions/workflow_actions";
import { listDataSources } from "../../../actions/datasource_actions"; import { listDataSources } from "../../../actions/datasource_actions";
import { TestProfile } from "@/app/lib/types/testing_types";
export function App({ export function App({
projectId, projectId,
useRag,
}: { }: {
projectId: string; projectId: string;
useRag: boolean;
}) { }) {
const [selectorKey, setSelectorKey] = useState(0); const [selectorKey, setSelectorKey] = useState(0);
const [workflow, setWorkflow] = useState<WithStringId<z.infer<typeof Workflow>> | null>(null); const [workflow, setWorkflow] = useState<WithStringId<z.infer<typeof Workflow>> | null>(null);
@ -106,6 +107,7 @@ export function App({
publishedWorkflowId={publishedWorkflowId} publishedWorkflowId={publishedWorkflowId}
handleShowSelector={handleShowSelector} handleShowSelector={handleShowSelector}
handleCloneVersion={handleCloneVersion} handleCloneVersion={handleCloneVersion}
useRag={useRag}
/>} />}
</> </>
} }

View file

@ -1,5 +1,8 @@
import { Metadata } from "next"; import { Metadata } from "next";
import { App } from "./app"; import { App } from "./app";
import { USE_RAG } from "@/app/lib/feature_flags";
export const dynamic = 'force-dynamic';
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Workflow" title: "Workflow"
@ -12,5 +15,6 @@ export default async function Page({
}) { }) {
return <App return <App
projectId={params.projectId} projectId={params.projectId}
useRag={USE_RAG}
/>; />;
} }

View file

@ -534,12 +534,14 @@ export function WorkflowEditor({
publishedWorkflowId, publishedWorkflowId,
handleShowSelector, handleShowSelector,
handleCloneVersion, handleCloneVersion,
useRag,
}: { }: {
dataSources: WithStringId<z.infer<typeof DataSource>>[]; dataSources: WithStringId<z.infer<typeof DataSource>>[];
workflow: WithStringId<z.infer<typeof Workflow>>; workflow: WithStringId<z.infer<typeof Workflow>>;
publishedWorkflowId: string | null; publishedWorkflowId: string | null;
handleShowSelector: () => void; handleShowSelector: () => void;
handleCloneVersion: (workflowId: string) => void; handleCloneVersion: (workflowId: string) => void;
useRag: boolean;
}) { }) {
const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, { const [state, dispatch] = useReducer<Reducer<State, Action>>(reducer, {
patches: [], patches: [],
@ -873,6 +875,7 @@ export function WorkflowEditor({
dataSources={dataSources} dataSources={dataSources}
handleUpdate={handleUpdateAgent.bind(null, state.present.selection.name)} handleUpdate={handleUpdateAgent.bind(null, state.present.selection.name)}
handleClose={handleUnselectAgent} handleClose={handleUnselectAgent}
useRag={useRag}
/>} />}
{state.present.selection?.type === "tool" && <ToolConfig {state.present.selection?.type === "tool" && <ToolConfig
key={state.present.selection.name} key={state.present.selection.name}

View file

@ -1,11 +1,8 @@
import '../lib/loadenv'; import '../lib/loadenv';
import FirecrawlApp from '@mendable/firecrawl-js';
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"; import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { z } from 'zod'; import { z } from 'zod';
import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb'; import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb';
import { EmbeddingRecord } from "../lib/types/datasource_types"; import { EmbeddingRecord, DataSourceDoc, DataSource } from "../lib/types/datasource_types";
import { DataSourceDoc } from "../lib/types/datasource_types";
import { DataSource } from "../lib/types/datasource_types";
import { WithId } from 'mongodb'; import { WithId } from 'mongodb';
import { embedMany } from 'ai'; import { embedMany } from 'ai';
import { embeddingModel } from '../lib/embedding'; import { embeddingModel } from '../lib/embedding';
@ -14,6 +11,7 @@ import { PrefixLogger } from "../lib/utils";
import { GoogleGenerativeAI } from "@google/generative-ai"; import { GoogleGenerativeAI } from "@google/generative-ai";
import { GetObjectCommand } from "@aws-sdk/client-s3"; import { GetObjectCommand } from "@aws-sdk/client-s3";
import { uploadsS3Client } from '../lib/uploads_s3_client'; import { uploadsS3Client } from '../lib/uploads_s3_client';
import crypto from 'crypto';
const splitter = new RecursiveCharacterTextSplitter({ const splitter = new RecursiveCharacterTextSplitter({
separators: ['\n\n', '\n', '. ', '.', ''], separators: ['\n\n', '\n', '. ', '.', ''],
@ -31,11 +29,11 @@ const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY || '');
async function getFileContent(s3Key: string): Promise<Buffer> { async function getFileContent(s3Key: string): Promise<Buffer> {
const command = new GetObjectCommand({ const command = new GetObjectCommand({
Bucket: process.env.UPLOADS_S3_BUCKET, Bucket: process.env.RAG_UPLOADS_S3_BUCKET,
Key: s3Key, Key: s3Key,
}); });
const response = await uploadsS3Client.send(command); const response = await uploadsS3Client.send(command);
const chunks: Buffer[] = []; const chunks: Uint8Array[] = [];
for await (const chunk of response.Body as any) { for await (const chunk of response.Body as any) {
chunks.push(chunk); chunks.push(chunk);
} }

View file

@ -3,28 +3,16 @@ import FirecrawlApp from '@mendable/firecrawl-js';
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"; import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { z } from 'zod'; import { z } from 'zod';
import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb'; import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb';
import { EmbeddingRecord } from "../lib/types/datasource_types"; import { EmbeddingRecord, DataSourceDoc, DataSource } from "../lib/types/datasource_types";
import { DataSourceDoc } from "../lib/types/datasource_types";
import { DataSource } from "../lib/types/datasource_types";
import { WithId } from 'mongodb'; import { WithId } from 'mongodb';
import { embedMany } from 'ai'; import { embedMany } from 'ai';
import { embeddingModel } from '../lib/embedding'; import { embeddingModel } from '../lib/embedding';
import { qdrantClient } from '../lib/qdrant'; import { qdrantClient } from '../lib/qdrant';
import { PrefixLogger } from "../lib/utils"; import { PrefixLogger } from "../lib/utils";
import crypto from 'crypto';
const firecrawl = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY }); const firecrawl = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY });
const firecrawlHttpAuth = {
'Authorization': `Bearer ${process.env.FIRECRAWL_API_KEY}`,
}
type Webpage = {
title: string,
url: string,
markdown: string,
html: string,
}
const splitter = new RecursiveCharacterTextSplitter({ const splitter = new RecursiveCharacterTextSplitter({
separators: ['\n\n', '\n', '. ', '.', ''], separators: ['\n\n', '\n', '. ', '.', ''],
chunkSize: 1024, chunkSize: 1024,
@ -36,18 +24,6 @@ const minute = 60 * second;
const hour = 60 * minute; const hour = 60 * minute;
const day = 24 * hour; const day = 24 * hour;
const firecrawlStatusPollInterval = 60 * second;
/*
const source: z.infer<typeof SourceSchema> = {
_id: new ObjectId(),
url: "https://www.example.com",
type: "web",
status: 'processing',
createdAt: new Date().toISOString(),
};
*/
async function retryable<T>(fn: () => Promise<T>, maxAttempts: number = 3): Promise<T> { async function retryable<T>(fn: () => Promise<T>, maxAttempts: number = 3): Promise<T> {
let attempts = 0; let attempts = 0;
while (true) { while (true) {

View file

@ -0,0 +1,46 @@
# syntax=docker.io/docker/dockerfile:1
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED=1
RUN \
if [ -f yarn.lock ]; then yarn run build; \
elif [ -f package-lock.json ]; then npm run build; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \
else echo "Lockfile not found." && exit 1; \
fi
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

View file

@ -10,23 +10,31 @@ services:
environment: environment:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING} - MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY}
- OXYLABS_USERNAME=${OXYLABS_USERNAME}
- OXYLABS_PASSWORD=${OXYLABS_PASSWORD}
- CHAT_WIDGET_SESSION_JWT_SECRET=${CHAT_WIDGET_SESSION_JWT_SECRET}
- AGENTS_API_URL=http://agents:3001
- AGENTS_API_KEY=${AGENTS_API_KEY}
- COPILOT_API_URL=http://copilot:3002
- COPILOT_API_KEY=${COPILOT_API_KEY}
- AUTH0_SECRET=${AUTH0_SECRET} - AUTH0_SECRET=${AUTH0_SECRET}
- AUTH0_BASE_URL=${AUTH0_BASE_URL} - AUTH0_BASE_URL=${AUTH0_BASE_URL}
- AUTH0_ISSUER_BASE_URL=${AUTH0_ISSUER_BASE_URL} - AUTH0_ISSUER_BASE_URL=${AUTH0_ISSUER_BASE_URL}
- AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID} - AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}
- AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET} - AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET}
- AGENTS_API_URL=http://agents:3001
- AGENTS_API_KEY=${AGENTS_API_KEY}
- COPILOT_API_URL=http://copilot:3002
- COPILOT_API_KEY=${COPILOT_API_KEY}
- REDIS_URL=redis://redis:6379 - REDIS_URL=redis://redis:6379
- USE_RAG=${USE_RAG}
- QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY}
- USE_RAG_UPLOADS=${USE_RAG_UPLOADS}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- RAG_UPLOADS_S3_BUCKET=${RAG_UPLOADS_S3_BUCKET}
- RAG_UPLOADS_S3_REGION=${RAG_UPLOADS_S3_REGION}
- USE_RAG_SCRAPING=${USE_RAG_SCRAPING}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY}
- USE_CHAT_WIDGET=${USE_CHAT_WIDGET}
- CHAT_WIDGET_HOST=http://localhost:3006
- CHAT_WIDGET_SESSION_JWT_SECRET=${CHAT_WIDGET_SESSION_JWT_SECRET}
- MAX_QUERIES_PER_MINUTE=${MAX_QUERIES_PER_MINUTE} - MAX_QUERIES_PER_MINUTE=${MAX_QUERIES_PER_MINUTE}
- MAX_PROJECTS_PER_USER=${MAX_PROJECTS_PER_USER} - MAX_PROJECTS_PER_USER=${MAX_PROJECTS_PER_USER}
- CHAT_WIDGET_HOST=${CHAT_WIDGET_HOST}
restart: unless-stopped restart: unless-stopped
agents: agents:
@ -51,16 +59,6 @@ services:
- API_KEY=${COPILOT_API_KEY} - API_KEY=${COPILOT_API_KEY}
restart: unless-stopped restart: unless-stopped
tools_webhook:
build:
context: ./apps/tools_webhook
dockerfile: Dockerfile
ports:
- "3005:3005"
environment:
- SIGNING_SECRET=${SIGNING_SECRET}
restart: unless-stopped
simulation_runner: simulation_runner:
build: build:
context: ./apps/simulation_runner context: ./apps/simulation_runner
@ -71,24 +69,60 @@ services:
- OPENAI_API_KEY=${OPENAI_API_KEY} - OPENAI_API_KEY=${OPENAI_API_KEY}
restart: unless-stopped restart: unless-stopped
docs: rag_files_worker:
build: build:
context: ./apps/docs context: ./apps/rowboat
dockerfile: scripts.Dockerfile
command: ["npm", "run", "ragFilesWorker"]
profiles: [ "rag_files_worker" ]
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- RAG_UPLOADS_S3_BUCKET=${RAG_UPLOADS_S3_BUCKET}
- RAG_UPLOADS_S3_REGION=${RAG_UPLOADS_S3_REGION}
- QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped
rag_urls_worker:
build:
context: ./apps/rowboat
dockerfile: scripts.Dockerfile
command: ["npm", "run", "ragUrlsWorker"]
profiles: [ "rag_urls_worker" ]
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- MONGODB_CONNECTION_STRING=${MONGODB_CONNECTION_STRING}
- FIRECRAWL_API_KEY=${FIRECRAWL_API_KEY}
- QDRANT_URL=${QDRANT_URL}
- QDRANT_API_KEY=${QDRANT_API_KEY}
restart: unless-stopped
tools_webhook:
build:
context: ./apps/tools_webhook
dockerfile: Dockerfile dockerfile: Dockerfile
profiles: [ "tools_webhook" ]
ports: ports:
- "8000:8000" - "3005:3005"
environment:
- SIGNING_SECRET=${SIGNING_SECRET}
restart: unless-stopped restart: unless-stopped
chat_widget: chat_widget:
build: build:
context: ./apps/chat_widget context: ./apps/chat_widget
dockerfile: Dockerfile dockerfile: Dockerfile
profiles: [ "chat_widget" ]
ports: ports:
- "3006:3006" - "3006:3006"
environment: environment:
- PORT=3006 - PORT=3006
- CHAT_WIDGET_HOST=${CHAT_WIDGET_HOST} - CHAT_WIDGET_HOST=http://localhost:3006
- ROWBOAT_HOST=${ROWBOAT_HOST} - ROWBOAT_HOST=http://localhost:3000
restart: unless-stopped restart: unless-stopped
redis: redis:
@ -96,3 +130,12 @@ services:
ports: ports:
- "6379:6379" - "6379:6379"
restart: unless-stopped restart: unless-stopped
docs:
build:
context: ./apps/docs
dockerfile: Dockerfile
profiles: [ "docs" ]
ports:
- "8000:8000"
restart: unless-stopped