mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-19 18:35:18 +02:00
Remove outer layer for projects page
This commit is contained in:
parent
d87faf4ec6
commit
72f8c6815a
4 changed files with 193 additions and 214 deletions
|
|
@ -102,11 +102,6 @@ html, body {
|
||||||
transition-all duration-200 ease-in-out;
|
transition-all duration-200 ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
@apply shadow-[0_4px_12px_rgba(0,0,0,0.06)]
|
|
||||||
transform translate-y-[-1px];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Update input styles */
|
/* Update input styles */
|
||||||
input, textarea, select {
|
input, textarea, select {
|
||||||
@apply rounded-lg border-[#E5E7EB] dark:border-[#2E2E30]
|
@apply rounded-lg border-[#E5E7EB] dark:border-[#2E2E30]
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export const USE_CHAT_WIDGET = process.env.USE_CHAT_WIDGET === 'true';
|
||||||
export const USE_AUTH = process.env.USE_AUTH === 'true';
|
export const USE_AUTH = process.env.USE_AUTH === 'true';
|
||||||
|
|
||||||
// Hardcoded flags
|
// Hardcoded flags
|
||||||
export const USE_MULTIPLE_PROJECTS = false;
|
export const USE_MULTIPLE_PROJECTS = true;
|
||||||
export const USE_TESTING_FEATURE = false;
|
export const USE_TESTING_FEATURE = false;
|
||||||
export const USE_VOICE_FEATURE = false;
|
export const USE_VOICE_FEATURE = false;
|
||||||
export const USE_TRANSFER_CONTROL_OPTIONS = false;
|
export const USE_TRANSFER_CONTROL_OPTIONS = false;
|
||||||
|
|
@ -11,7 +11,6 @@ import { templates, starting_copilot_prompts } from "@/app/lib/project_templates
|
||||||
import { SectionHeading } from "@/components/ui/section-heading";
|
import { SectionHeading } from "@/components/ui/section-heading";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { SearchProjects } from "./components/search-projects";
|
import { SearchProjects } from "./components/search-projects";
|
||||||
import { CustomPromptCard } from "./components/custom-prompt-card";
|
|
||||||
import { Submit } from "./components/submit-button";
|
import { Submit } from "./components/submit-button";
|
||||||
import { PageHeading } from "@/components/ui/page-heading";
|
import { PageHeading } from "@/components/ui/page-heading";
|
||||||
import { USE_MULTIPLE_PROJECTS } from "@/app/lib/feature_flags";
|
import { USE_MULTIPLE_PROJECTS } from "@/app/lib/feature_flags";
|
||||||
|
|
@ -297,235 +296,220 @@ export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"min-h-screen flex flex-col",
|
"flex-1 px-12 pt-4 pb-32"
|
||||||
tokens.colors.light.background,
|
|
||||||
tokens.colors.dark.background
|
|
||||||
)}>
|
)}>
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
"flex-1 px-12 pt-4 pb-32"
|
USE_MULTIPLE_PROJECTS
|
||||||
|
? "grid grid-cols-1 lg:grid-cols-[1fr,2fr] gap-8 mt-8"
|
||||||
|
: "mt-8 -mx-12"
|
||||||
)}>
|
)}>
|
||||||
<PageHeading
|
{/* Left side: Project Selection */}
|
||||||
title={USE_MULTIPLE_PROJECTS ? "Projects" : "Let's get started"}
|
{USE_MULTIPLE_PROJECTS && isProjectPaneOpen && (
|
||||||
description={USE_MULTIPLE_PROJECTS
|
<div className="overflow-auto">
|
||||||
? "Select an existing project or create a new one"
|
<SearchProjects
|
||||||
: "Create a multi-agent assistant in minutes"
|
projects={projects}
|
||||||
}
|
isLoading={isLoading}
|
||||||
/>
|
heading="Select an existing assistant"
|
||||||
|
className="h-full"
|
||||||
|
onClose={() => setIsProjectPaneOpen(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Right side: Project Creation */}
|
||||||
<div className={clsx(
|
<div className={clsx(
|
||||||
USE_MULTIPLE_PROJECTS
|
"overflow-auto",
|
||||||
? "grid grid-cols-1 lg:grid-cols-[1fr,2fr] gap-8 mt-8"
|
!USE_MULTIPLE_PROJECTS && "max-w-none px-12 py-12",
|
||||||
: "mt-8 -mx-12"
|
USE_MULTIPLE_PROJECTS && !isProjectPaneOpen && "col-span-full"
|
||||||
)}>
|
)}>
|
||||||
{/* Left side: Project Selection */}
|
<section className={clsx(
|
||||||
{USE_MULTIPLE_PROJECTS && isProjectPaneOpen && (
|
"card h-full",
|
||||||
<div className="overflow-auto">
|
!USE_MULTIPLE_PROJECTS && "px-24",
|
||||||
<SearchProjects
|
USE_MULTIPLE_PROJECTS && "px-8"
|
||||||
projects={projects}
|
|
||||||
isLoading={isLoading}
|
|
||||||
heading="Select an existing project"
|
|
||||||
subheading="Choose from your projects"
|
|
||||||
className="h-full"
|
|
||||||
onClose={() => setIsProjectPaneOpen(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Right side: Project Creation */}
|
|
||||||
<div className={clsx(
|
|
||||||
"overflow-auto",
|
|
||||||
!USE_MULTIPLE_PROJECTS && "max-w-none px-12 py-12",
|
|
||||||
USE_MULTIPLE_PROJECTS && !isProjectPaneOpen && "col-span-full"
|
|
||||||
)}>
|
)}>
|
||||||
<section className={clsx(
|
{USE_MULTIPLE_PROJECTS && (
|
||||||
"card h-full",
|
<div className="px-4 pt-4 pb-6 flex justify-between items-center">
|
||||||
!USE_MULTIPLE_PROJECTS && "px-24",
|
<SectionHeading>
|
||||||
USE_MULTIPLE_PROJECTS && "px-8"
|
Create a new assistant
|
||||||
)}>
|
</SectionHeading>
|
||||||
{USE_MULTIPLE_PROJECTS && (
|
{!isProjectPaneOpen && (
|
||||||
<div className="pt-12 flex justify-between items-center">
|
<Button
|
||||||
<SectionHeading subheading="Set up a new AI assistant">
|
onClick={() => setIsProjectPaneOpen(true)}
|
||||||
Create a new project
|
variant="primary"
|
||||||
|
size="md"
|
||||||
|
startContent={<FolderOpenIcon className="w-4 h-4" />}
|
||||||
|
>
|
||||||
|
View Projects
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form
|
||||||
|
id="create-project-form"
|
||||||
|
action={handleSubmit}
|
||||||
|
onSubmit={(e) => {
|
||||||
|
// Prevent default form submission
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
handleSubmit(formData);
|
||||||
|
}}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
className="pt-6 pb-16 space-y-12"
|
||||||
|
>
|
||||||
|
{/* Tab Section */}
|
||||||
|
<div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<SectionHeading>
|
||||||
|
✨ Get started
|
||||||
</SectionHeading>
|
</SectionHeading>
|
||||||
{!isProjectPaneOpen && (
|
|
||||||
<Button
|
|
||||||
onClick={() => setIsProjectPaneOpen(true)}
|
|
||||||
variant="primary"
|
|
||||||
size="md"
|
|
||||||
startContent={<FolderOpenIcon className="w-4 h-4" />}
|
|
||||||
>
|
|
||||||
View Projects
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
<form
|
|
||||||
id="create-project-form"
|
|
||||||
action={handleSubmit}
|
|
||||||
onSubmit={(e) => {
|
|
||||||
// Prevent default form submission
|
|
||||||
e.preventDefault();
|
|
||||||
const formData = new FormData(e.currentTarget);
|
|
||||||
handleSubmit(formData);
|
|
||||||
}}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
className="pt-12 pb-16 space-y-12"
|
|
||||||
>
|
|
||||||
{/* Tab Section */}
|
|
||||||
<div>
|
|
||||||
<div className="mb-5">
|
|
||||||
<SectionHeading>
|
|
||||||
✨ Get started
|
|
||||||
</SectionHeading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
<div className="flex gap-6 relative">
|
<div className="flex gap-6 relative">
|
||||||
|
<Button
|
||||||
|
variant={selectedTab === TabType.Describe ? 'primary' : 'tertiary'}
|
||||||
|
size="md"
|
||||||
|
onClick={() => handleTabChange(TabType.Describe)}
|
||||||
|
className={selectedTab === TabType.Describe ? selectedTabStyles : unselectedTabStyles}
|
||||||
|
>
|
||||||
|
Describe your assistant
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedTab === TabType.Blank ? 'primary' : 'tertiary'}
|
||||||
|
size="md"
|
||||||
|
onClick={handleBlankTemplateClick}
|
||||||
|
type="button"
|
||||||
|
className={selectedTab === TabType.Blank ? selectedTabStyles : unselectedTabStyles}
|
||||||
|
>
|
||||||
|
Start from a blank template
|
||||||
|
</Button>
|
||||||
|
<div className="relative" ref={dropdownRef}>
|
||||||
<Button
|
<Button
|
||||||
variant={selectedTab === TabType.Describe ? 'primary' : 'tertiary'}
|
variant={selectedTab === TabType.Example ? 'primary' : 'tertiary'}
|
||||||
size="md"
|
size="md"
|
||||||
onClick={() => handleTabChange(TabType.Describe)}
|
onClick={(e) => {
|
||||||
className={selectedTab === TabType.Describe ? selectedTabStyles : unselectedTabStyles}
|
e.preventDefault();
|
||||||
>
|
e.stopPropagation();
|
||||||
Decsribe your assistant
|
setIsExamplesDropdownOpen(!isExamplesDropdownOpen);
|
||||||
</Button>
|
}}
|
||||||
<Button
|
|
||||||
variant={selectedTab === TabType.Blank ? 'primary' : 'tertiary'}
|
|
||||||
size="md"
|
|
||||||
onClick={handleBlankTemplateClick}
|
|
||||||
type="button"
|
type="button"
|
||||||
className={selectedTab === TabType.Blank ? selectedTabStyles : unselectedTabStyles}
|
className={selectedTab === TabType.Example ? selectedTabStyles : unselectedTabStyles}
|
||||||
|
endContent={
|
||||||
|
<svg className="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Start from a blank template
|
Customize an existing example
|
||||||
</Button>
|
</Button>
|
||||||
<div className="relative" ref={dropdownRef}>
|
|
||||||
<Button
|
{isExamplesDropdownOpen && (
|
||||||
variant={selectedTab === TabType.Example ? 'primary' : 'tertiary'}
|
<div className="absolute z-10 mt-2 min-w-[200px] max-w-[240px] rounded-lg shadow-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||||
size="md"
|
<div className="py-1">
|
||||||
onClick={(e) => {
|
{Object.entries(starting_copilot_prompts)
|
||||||
e.preventDefault();
|
.filter(([name]) => name !== 'Blank Template')
|
||||||
e.stopPropagation();
|
.map(([name]) => (
|
||||||
setIsExamplesDropdownOpen(!isExamplesDropdownOpen);
|
<Button
|
||||||
}}
|
key={name}
|
||||||
type="button"
|
variant="tertiary"
|
||||||
className={selectedTab === TabType.Example ? selectedTabStyles : unselectedTabStyles}
|
size="sm"
|
||||||
endContent={
|
className="w-full justify-start text-left text-sm py-1.5"
|
||||||
<svg className="w-4 h-4" viewBox="0 0 20 20" fill="currentColor">
|
onClick={(e) => {
|
||||||
<path fillRule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" />
|
e.preventDefault();
|
||||||
</svg>
|
e.stopPropagation();
|
||||||
}
|
handleExampleSelect(name);
|
||||||
>
|
}}
|
||||||
Customize an existing example
|
type="button"
|
||||||
</Button>
|
>
|
||||||
|
{name}
|
||||||
{isExamplesDropdownOpen && (
|
</Button>
|
||||||
<div className="absolute z-10 mt-2 min-w-[200px] max-w-[240px] rounded-lg shadow-lg bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
))
|
||||||
<div className="py-1">
|
}
|
||||||
{Object.entries(starting_copilot_prompts)
|
|
||||||
.filter(([name]) => name !== 'Blank Template')
|
|
||||||
.map(([name]) => (
|
|
||||||
<Button
|
|
||||||
key={name}
|
|
||||||
variant="tertiary"
|
|
||||||
size="sm"
|
|
||||||
className="w-full justify-start text-left text-sm py-1.5"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
handleExampleSelect(name);
|
|
||||||
}}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</Button>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Custom Prompt Section - Only show when needed */}
|
||||||
|
{(selectedTab === TabType.Describe || selectedTab === TabType.Example) && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className={largeSectionHeaderStyles}>
|
||||||
|
{selectedTab === TabType.Describe ? '✏️ What do you want to build?' : '✏️ Customize the description'}
|
||||||
|
</label>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Textarea
|
||||||
|
value={customPrompt}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCustomPrompt(e.target.value);
|
||||||
|
setPromptError(null);
|
||||||
|
}}
|
||||||
|
placeholder="Example: Create a customer support assistant that can handle product inquiries and returns"
|
||||||
|
className={clsx(
|
||||||
|
textareaStyles,
|
||||||
|
"text-base",
|
||||||
|
"text-gray-900 dark:text-gray-100",
|
||||||
|
promptError && "border-red-500 focus:ring-red-500/20",
|
||||||
|
!customPrompt && emptyTextareaStyles
|
||||||
|
)}
|
||||||
|
style={{ minHeight: "120px" }}
|
||||||
|
autoFocus
|
||||||
|
autoResize
|
||||||
|
required={isNotBlankTemplate(selectedTab)}
|
||||||
|
/>
|
||||||
|
{promptError && (
|
||||||
|
<p className="text-sm text-red-500">
|
||||||
|
{promptError}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Name Section */}
|
{selectedTab === TabType.Blank && (
|
||||||
{USE_MULTIPLE_PROJECTS && (
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-4">
|
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
<label className={largeSectionHeaderStyles}>
|
👇 Click “Create assistant” below to get started
|
||||||
Name
|
</p>
|
||||||
</label>
|
|
||||||
<Textarea
|
|
||||||
required
|
|
||||||
name="name"
|
|
||||||
value={name}
|
|
||||||
onChange={(e) => setName(e.target.value)}
|
|
||||||
className={clsx(
|
|
||||||
textareaStyles,
|
|
||||||
"min-h-[60px]",
|
|
||||||
"text-base",
|
|
||||||
"text-gray-900 dark:text-gray-100"
|
|
||||||
)}
|
|
||||||
placeholder={defaultName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Custom Prompt Section - Only show when needed */}
|
|
||||||
{(selectedTab === TabType.Describe || selectedTab === TabType.Example) && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<label className={largeSectionHeaderStyles}>
|
|
||||||
{selectedTab === TabType.Describe ? '✏️ What do you want to build?' : '✏️ Customize the description'}
|
|
||||||
</label>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Textarea
|
|
||||||
value={customPrompt}
|
|
||||||
onChange={(e) => {
|
|
||||||
setCustomPrompt(e.target.value);
|
|
||||||
setPromptError(null);
|
|
||||||
}}
|
|
||||||
placeholder="Example: Create a customer support assistant that can handle product inquiries and returns"
|
|
||||||
className={clsx(
|
|
||||||
textareaStyles,
|
|
||||||
"text-base",
|
|
||||||
"text-gray-900 dark:text-gray-100",
|
|
||||||
promptError && "border-red-500 focus:ring-red-500/20",
|
|
||||||
!customPrompt && emptyTextareaStyles
|
|
||||||
)}
|
|
||||||
style={{ minHeight: "120px" }}
|
|
||||||
autoFocus
|
|
||||||
autoResize
|
|
||||||
required={isNotBlankTemplate(selectedTab)}
|
|
||||||
/>
|
|
||||||
{promptError && (
|
|
||||||
<p className="text-sm text-red-500">
|
|
||||||
{promptError}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedTab === TabType.Blank && (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
|
||||||
👇 Click “Create assistant” below to get started
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Submit Button */}
|
|
||||||
<div className="pt-1 w-full -mt-4">
|
|
||||||
<Submit />
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
)}
|
||||||
</section>
|
|
||||||
</div>
|
{/* Name Section */}
|
||||||
|
{USE_MULTIPLE_PROJECTS && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<label className={largeSectionHeaderStyles}>
|
||||||
|
🏷️ Name the project
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
required
|
||||||
|
name="name"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
className={clsx(
|
||||||
|
textareaStyles,
|
||||||
|
"min-h-[60px]",
|
||||||
|
"text-base",
|
||||||
|
"text-gray-900 dark:text-gray-100"
|
||||||
|
)}
|
||||||
|
placeholder={defaultName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<div className="pt-1 w-full -mt-4">
|
||||||
|
<Submit />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ interface SearchProjectsProps {
|
||||||
projects: z.infer<typeof Project>[];
|
projects: z.infer<typeof Project>[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
heading: string;
|
heading: string;
|
||||||
subheading: string;
|
subheading?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue