From 9d54d971d2e9027823f0821671775fdcf0e2fa0a Mon Sep 17 00:00:00 2001
From: arkml
Date: Wed, 30 Jul 2025 09:01:08 +0530
Subject: [PATCH] Deployment settings (#189)
* moved settings to a modal inside the build view under deployment
* project name is a editable field in the build view
* project name is propogated correctly
* remove project name from settings modal
* split settings into phone and api
* added chat widget option to the deploy settings
* bring back top level settings tab with few options
* removed cancel option from twilio settings
---
.../app/projects/[projectId]/config/app.tsx | 84 ++++----
.../[projectId]/config/components/project.tsx | 84 ++++----
.../[projectId]/config/components/voice.tsx | 20 +-
.../app/projects/[projectId]/config/page.tsx | 9 +-
.../app/projects/[projectId]/workflow/app.tsx | 11 ++
.../projects/[projectId]/workflow/page.tsx | 1 +
.../[projectId]/workflow/workflow_editor.tsx | 185 ++++++++++++++++--
apps/rowboat/app/projects/layout/menu.tsx | 9 +-
8 files changed, 274 insertions(+), 129 deletions(-)
diff --git a/apps/rowboat/app/projects/[projectId]/config/app.tsx b/apps/rowboat/app/projects/[projectId]/config/app.tsx
index 5c8ecff1..e7b0d26b 100644
--- a/apps/rowboat/app/projects/[projectId]/config/app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/config/app.tsx
@@ -1,7 +1,8 @@
'use client';
import { Metadata } from "next";
-import { Spinner, Textarea, Button, Dropdown, DropdownMenu, DropdownItem, DropdownTrigger, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Input, useDisclosure, Divider, Tab, Tabs } from "@heroui/react";
+import { Spinner, Dropdown, DropdownMenu, DropdownItem, DropdownTrigger, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Input, useDisclosure, Divider, Textarea } from "@heroui/react";
+import { Button } from "@/components/ui/button";
import { ReactNode, useEffect, useState } from "react";
import { getProjectConfig, updateProjectName, updateWebhookUrl, createApiKey, deleteApiKey, listApiKeys, deleteProject, rotateSecret } from "../../../actions/project_actions";
import { CopyButton } from "../../../../components/common/copy-button";
@@ -14,8 +15,7 @@ import { RelativeTime } from "@primer/react";
import { Label } from "../../../lib/components/label";
import { FormSection } from "../../../lib/components/form-section";
import { Panel } from "@/components/common/panel-common";
-import { ProjectSection } from './components/project';
-import { VoiceSection } from "./components/voice";
+import { ProjectSection, SimpleProjectSection } from './components/project';
export const metadata: Metadata = {
title: "Project config",
@@ -187,11 +187,11 @@ export function ApiKeysSection({
API keys are used to authenticate requests to the Rowboat API.
}
- variant="flat"
- isDisabled={loading}
+ variant="primary"
+ disabled={loading}
>
Create API key
@@ -321,10 +321,10 @@ export function SecretSection({
/>
@@ -477,9 +477,8 @@ export function DeleteProjectSection({
@@ -508,13 +507,13 @@ export function DeleteProjectSection({
-
- {existingConfig ? (
+ {existingConfig && (
Delete Configuration
- ) : (
- {
- setFormState({
- phone: '',
- accountSid: '',
- authToken: '',
- label: ''
- });
- setError(null);
- setIsDirty(false);
- }}
- disabled={loading}
- >
- Cancel
-
)}
diff --git a/apps/rowboat/app/projects/[projectId]/config/page.tsx b/apps/rowboat/app/projects/[projectId]/config/page.tsx
index d5154550..1f4fb70f 100644
--- a/apps/rowboat/app/projects/[projectId]/config/page.tsx
+++ b/apps/rowboat/app/projects/[projectId]/config/page.tsx
@@ -1,10 +1,9 @@
import { Metadata } from "next";
-import App from "./app";
-import { USE_CHAT_WIDGET } from "@/app/lib/feature_flags";
+import { SimpleConfigApp } from "./app";
import { requireActiveBillingSubscription } from '@/app/lib/billing';
export const metadata: Metadata = {
- title: "Project config",
+ title: "Project Settings",
};
export default async function Page(
@@ -16,9 +15,7 @@ export default async function Page(
) {
const params = await props.params;
await requireActiveBillingSubscription();
- return ;
}
\ No newline at end of file
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
index c3933479..670fa299 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/app.tsx
@@ -20,6 +20,7 @@ export function App({
useRagS3Uploads,
useRagScraping,
defaultModel,
+ chatWidgetHost,
}: {
projectId: string;
useRag: boolean;
@@ -27,6 +28,7 @@ export function App({
useRagS3Uploads: boolean;
useRagScraping: boolean;
defaultModel: string;
+ chatWidgetHost: string;
}) {
const [mode, setMode] = useState<'draft' | 'live'>('draft');
const [project, setProject] = useState> | null>(null);
@@ -84,6 +86,13 @@ export function App({
setDataSources(updatedDataSources);
}, [projectId]);
+ const handleProjectConfigUpdate = useCallback(async () => {
+ // Refresh project config when project name or other settings change
+ const updatedProjectConfig = await getProjectConfig(projectId);
+ setProject(updatedProjectConfig);
+ setProjectConfig(updatedProjectConfig);
+ }, [projectId]);
+
// Auto-update data sources when there are pending ones
useEffect(() => {
if (!dataSources) return;
@@ -144,6 +153,8 @@ export function App({
onRevertToLive={handleRevertToLive}
onProjectToolsUpdated={handleProjectToolsUpdate}
onDataSourcesUpdated={handleDataSourcesUpdate}
+ onProjectConfigUpdated={handleProjectConfigUpdate}
+ chatWidgetHost={chatWidgetHost}
/>}
>
}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
index 224bc223..844b7105 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/page.tsx
@@ -42,6 +42,7 @@ export default async function Page(
useRagS3Uploads={USE_RAG_S3_UPLOADS}
useRagScraping={USE_RAG_SCRAPING}
defaultModel={DEFAULT_MODEL}
+ chatWidgetHost={process.env.CHAT_WIDGET_HOST || ''}
/>
);
}
diff --git a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
index 46688a8f..8beb38ec 100644
--- a/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
+++ b/apps/rowboat/app/projects/[projectId]/workflow/workflow_editor.tsx
@@ -13,7 +13,7 @@ import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Spinner,
import { PromptConfig } from "../entities/prompt_config";
import { DataSourceConfig } from "../entities/datasource_config";
import { RelativeTime } from "@primer/react";
-import { USE_PRODUCT_TOUR } from "@/app/lib/feature_flags";
+import { USE_PRODUCT_TOUR, USE_CHAT_WIDGET } from "@/app/lib/feature_flags";
import {
ResizableHandle,
@@ -23,14 +23,19 @@ import {
import { Copilot } from "../copilot/app";
import { publishWorkflow } from "@/app/actions/project_actions";
import { saveWorkflow } from "@/app/actions/project_actions";
+import { updateProjectName } from "@/app/actions/project_actions";
import { BackIcon, HamburgerIcon, WorkflowIcon } from "../../../lib/components/icons";
-import { CopyIcon, ImportIcon, Layers2Icon, RadioIcon, RedoIcon, ServerIcon, Sparkles, UndoIcon, RocketIcon, PenLine, AlertTriangle, DownloadIcon, XIcon } from "lucide-react";
+import { CopyIcon, ImportIcon, Layers2Icon, RadioIcon, RedoIcon, ServerIcon, Sparkles, UndoIcon, RocketIcon, PenLine, AlertTriangle, DownloadIcon, XIcon, SettingsIcon, ChevronDownIcon, PhoneIcon, MessageCircleIcon } from "lucide-react";
import { EntityList } from "./entity_list";
import { ProductTour } from "@/components/common/product-tour";
import { ModelsResponse } from "@/app/lib/types/billing_types";
import { AgentGraphVisualizer } from "../entities/AgentGraphVisualizer";
import { Panel } from "@/components/common/panel-common";
import { Button as CustomButton } from "@/components/ui/button";
+import { ConfigApp } from "../config/app";
+import { InputField } from "@/app/lib/components/input-field";
+import { VoiceSection } from "../config/components/voice";
+import { ChatWidgetSection } from "../config/components/project";
enablePatches();
@@ -599,6 +604,8 @@ export function WorkflowEditor({
onRevertToLive,
onProjectToolsUpdated,
onDataSourcesUpdated,
+ onProjectConfigUpdated,
+ chatWidgetHost,
}: {
projectId: string;
dataSources: WithStringId>[];
@@ -616,6 +623,8 @@ export function WorkflowEditor({
onRevertToLive: () => void;
onProjectToolsUpdated?: () => void;
onDataSourcesUpdated?: () => void;
+ onProjectConfigUpdated?: () => void;
+ chatWidgetHost: string;
}) {
const [state, dispatch] = useReducer(reducer, {
@@ -652,6 +661,19 @@ export function WorkflowEditor({
// Modal state for revert confirmation
const { isOpen: isRevertModalOpen, onOpen: onRevertModalOpen, onClose: onRevertModalClose } = useDisclosure();
+
+ // Modal state for settings
+ const { isOpen: isSettingsModalOpen, onOpen: onSettingsModalOpen, onClose: onSettingsModalClose } = useDisclosure();
+
+ // Modal state for phone/Twilio configuration
+ const { isOpen: isPhoneModalOpen, onOpen: onPhoneModalOpen, onClose: onPhoneModalClose } = useDisclosure();
+
+ // Modal state for chat widget configuration
+ const { isOpen: isChatWidgetModalOpen, onOpen: onChatWidgetModalOpen, onClose: onChatWidgetModalClose } = useDisclosure();
+
+ // Project name state
+ const [localProjectName, setLocalProjectName] = useState(projectConfig.name || '');
+ const [projectNameError, setProjectNameError] = useState(null);
// Load agent order from localStorage on mount
// useEffect(() => {
@@ -877,10 +899,39 @@ export function WorkflowEditor({
}
}, [state.present.workflow, state.present.pendingChanges, processQueue, state]);
+ // Sync project name when projectConfig changes
+ useEffect(() => {
+ setLocalProjectName(projectConfig.name || '');
+ }, [projectConfig.name]);
+
function handlePlaygroundClick() {
setIsInitialState(false);
}
+ const validateProjectName = (value: string) => {
+ if (value.length === 0) {
+ setProjectNameError("Project name cannot be empty");
+ return false;
+ }
+ setProjectNameError(null);
+ return true;
+ };
+
+ const handleProjectNameChange = async (value: string) => {
+ setLocalProjectName(value);
+
+ if (validateProjectName(value)) {
+ try {
+ await updateProjectName(projectId, value);
+ // Trigger refresh of project config to update all references to project name
+ onProjectConfigUpdated?.();
+ } catch (error) {
+ setProjectNameError("Failed to update project name");
+ console.error('Failed to update project name:', error);
+ }
+ }
+ };
+
return (
+ {/* Project Name Editor */}
+
+
+
+
+
+
{state.present.publishing &&
}
@@ -979,16 +1044,52 @@ export function WorkflowEditor({
>
-
}
- data-tour-target="deploy"
- >
- Deploy
-
+
+ }
+ data-tour-target="deploy"
+ >
+ Deploy
+
+
+
+
+
+
+
+
+ }
+ onPress={onSettingsModalOpen}
+ >
+ API & SDK
+
+ }
+ onPress={onPhoneModalOpen}
+ >
+ Phone
+
+ }
+ onPress={onChatWidgetModalOpen}
+ >
+ Chat widget
+
+
+
+
+
+ {/* Settings Modal */}
+
+
+
+ API & SDK
+
+
+
+
+
+
+
+ {/* Phone/Twilio Modal */}
+
+
+
+ Phone Configuration
+
+
+
+
+
+
+
+ {/* Chat Widget Modal */}
+
+
+
+ Chat Widget
+
+
+
+
+
+
+
+
);
diff --git a/apps/rowboat/app/projects/layout/menu.tsx b/apps/rowboat/app/projects/layout/menu.tsx
index 5b92b5ac..b1eb42cb 100644
--- a/apps/rowboat/app/projects/layout/menu.tsx
+++ b/apps/rowboat/app/projects/layout/menu.tsx
@@ -1,7 +1,7 @@
'use client';
import { usePathname } from "next/navigation";
import Link from "next/link";
-import { SettingsIcon, WorkflowIcon, PlayIcon, LucideIcon } from "lucide-react";
+import { WorkflowIcon, PlayIcon, LucideIcon } from "lucide-react";
import MenuItem from "./components/menu-item";
interface NavLinkProps {
@@ -51,13 +51,6 @@ export default function Menu({
icon={PlayIcon}
selected={pathname.startsWith(`/projects/${projectId}/test`)}
/>
-
);
}