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

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

View file

@ -53,6 +53,7 @@ export async function createProject(formData: FormData) {
secret,
nextWorkflowNumber: 1,
testRunCounter: 0,
webhookUrl: 'http://tools_webhook:3005/tool_call',
});
// 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({
projectId,
useChatWidget,
chatWidgetHost,
}: {
projectId: string;
useChatWidget: boolean;
chatWidgetHost: string;
}) {
return <div className="flex flex-col h-full">
@ -586,7 +588,7 @@ export default function App({
<SecretSection projectId={projectId} />
<ApiKeysSection projectId={projectId} />
<WebhookUrlSection projectId={projectId} />
<ChatWidgetSection projectId={projectId} chatWidgetHost={chatWidgetHost} />
{useChatWidget && <ChatWidgetSection projectId={projectId} chatWidgetHost={chatWidgetHost} />}
<DeleteProjectSection projectId={projectId} />
</div>
</div>

View file

@ -1,5 +1,7 @@
import { Metadata } from "next";
import App from "./app";
import { USE_CHAT_WIDGET } from "@/app/lib/feature_flags";
export const metadata: Metadata = {
title: "Project config",
};
@ -13,6 +15,7 @@ export default function Page({
}) {
return <App
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 { USE_RAG } from "@/app/lib/feature_flags";
export default async function Layout({
params,
@ -7,10 +8,10 @@ export default async function Layout({
params: { projectId: string }
children: React.ReactNode
}) {
const useDataSources = process.env.USE_DATA_SOURCES === 'true';
const useRag = USE_RAG;
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">
{children}
</div>

View file

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

View file

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

View file

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

View file

@ -8,27 +8,17 @@ import { PlusIcon } from "lucide-react";
import { useRouter } from "next/navigation";
export function Form({
projectId
projectId,
useRagUploads,
useRagScraping,
}: {
projectId: string;
useRagUploads: boolean;
useRagScraping: boolean;
}) {
const [sourceType, setSourceType] = useState("");
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) {
const source = await createDataSource({
projectId,
@ -80,14 +70,11 @@ export function Form({
label="Select type"
selectedKeys={[sourceType]}
onChange={handleSourceTypeChange}
disabledKeys={[
...(useRagUploads ? [] : ['files']),
...(useRagScraping ? [] : ['urls']),
]}
>
{/* <SelectItem
key="crawl"
value="crawl"
startContent={<DataSourceIcon type="crawl" />}
>
Crawl URLs
</SelectItem> */}
<SelectItem
key="urls"
startContent={<DataSourceIcon type="urls" />}
@ -102,62 +89,6 @@ export function Form({
</SelectItem>
</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
action={createUrlsDataSource}
className="flex flex-col gap-4"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,28 +3,16 @@ import FirecrawlApp from '@mendable/firecrawl-js';
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
import { z } from 'zod';
import { dataSourceDocsCollection, dataSourcesCollection } from '../lib/mongodb';
import { EmbeddingRecord } from "../lib/types/datasource_types";
import { DataSourceDoc } from "../lib/types/datasource_types";
import { DataSource } from "../lib/types/datasource_types";
import { EmbeddingRecord, DataSourceDoc, DataSource } from "../lib/types/datasource_types";
import { WithId } from 'mongodb';
import { embedMany } from 'ai';
import { embeddingModel } from '../lib/embedding';
import { qdrantClient } from '../lib/qdrant';
import { PrefixLogger } from "../lib/utils";
import crypto from 'crypto';
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({
separators: ['\n\n', '\n', '. ', '.', ''],
chunkSize: 1024,
@ -36,18 +24,6 @@ const minute = 60 * second;
const hour = 60 * minute;
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> {
let attempts = 0;
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