Skip to content
This repository has been archived by the owner on Jan 5, 2025. It is now read-only.

Commit

Permalink
Merge pull request #411 from openchatai/ui-enhancements
Browse files Browse the repository at this point in the history
  • Loading branch information
faltawy authored Dec 18, 2023
2 parents fbd2d86 + c2a1987 commit 431cfdc
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 324 deletions.
20 changes: 20 additions & 0 deletions dashboard/app/(copilot)/_parts/SearchBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client';
import { useSearchModal } from '@/app/_store/searchModal';
import { Button } from '@/components/ui/button';
import { Search } from 'lucide-react'
import React from 'react'


export function SearchBtn() {
const [, setOpen] = useSearchModal();
return (
<Button
size='fit'
variant='secondary'
className='rounded-full p-2.5'
onClick={() => setOpen(true)}
>
<Search className="h-4 w-4" />
</Button>
)
}
4 changes: 3 additions & 1 deletion dashboard/app/(copilot)/copilot/[copilot_id]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { CopilotProvider } from "../_context/CopilotProvider";
import { Link } from "@/lib/router-events";
import { Logo } from "@/components/domain/Logo";
import { SearchBtn } from "../../_parts/SearchBtn";

type Props = {
children: React.ReactNode;
Expand Down Expand Up @@ -74,8 +75,9 @@ export default function CopilotLayout({ children, params }: Props) {
/>
</div>
</div>
<div className="mx-auto pb-5">
<div className="mx-auto pb-5 flex flex-col items-center gap-2">
<Separator className="mb-5" />
<SearchBtn />
<DropdownMenu modal={false}>
<TooltipProvider>
<Tooltip>
Expand Down
12 changes: 11 additions & 1 deletion dashboard/app/(main)/_parts/Aside.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
Bot,
Github,
PlaySquare,
} from "lucide-react";
import { Link } from "@/lib/router-events";
import { Logo } from "@/components/domain/Logo";
import { Tooltip } from "@/components/domain/Tooltip";
import { CopilotLayoutNavLink } from "@/app/(copilot)/_parts/CopilotNavLink";
import { Button, buttonVariants } from "@/components/ui/button";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";

export default function Aside() {
Expand All @@ -32,6 +33,15 @@ export default function Aside() {
<PlaySquare className="h-5 w-5" />
</Link>
</Tooltip>
<Tooltip content={<>Tutorials</>} side="right">
<Link
href="https://github.com/openchatai/opencopilot"
target="_blank"
className={cn("hover:text-primary text-accent-foreground/50 hover:bg-accent", buttonVariants({ size: "icon", variant: "link" }))}
>
<Github className="h-5 w-5" />
</Link>
</Tooltip>
</div>
</div>
<div className="border-t flex-center border-border px-5 py-2">
Expand Down
3 changes: 2 additions & 1 deletion dashboard/app/(main)/_parts/SearchModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as React from "react";
import {
Bot,
BotIcon,
FileStack,
HelpCircle,
User,
Expand Down Expand Up @@ -67,7 +68,7 @@ export function SearchModal() {
key={copilot.id}
onSelect={() => push("/copilot/" + copilot.id)}
>
<User className="mr-2 h-4 w-4" />
<BotIcon className="mr-2 h-4 w-4" />
<span>
{copilot.name}
<span className="hidden">{copilot.id}</span>
Expand Down
260 changes: 260 additions & 0 deletions dashboard/app/(main)/create/copilot/_parts/DefineActionsStep.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
'use client';
import { atom, useAtom } from "jotai";
import { useAsyncFn } from "react-use";
import { useWizard } from "react-use-wizard";
import { revalidateActions, useCreateCopilot } from "./CreateCopilotProvider";
import useSWR from "swr";
import _ from "lodash";
import { toast } from "@/components/ui/use-toast";
import { AlertDialog, AlertDialogContent, AlertDialogCancel, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
import { createActionByBotId, getActionsByBotId, importActionsFromSwagger } from "@/data/actions";
import Link from "next/link";
import { DropZone } from "@/components/domain/DropZone";
import { Button } from "@/components/ui/button";
import { Plus, Trash2, UploadCloud } from "lucide-react";
import { EmptyBlock } from "@/components/domain/EmptyBlock";
import { ActionForm } from "@/components/domain/action-form/ActionForm";
import { methodVariants } from "@/components/domain/MethodRenderer";
import { cn } from "@/lib/utils";

function GetActionsFromSwagger() {
const { state: { swaggerFiles }, dispatch } = useCreateCopilot();
return <div>
<div className="my-5">
<DropZone
multiple={false}
maxFiles={1}
accept={{ json: ["application/json"] }}
value={swaggerFiles || []}
onChange={(files) => {
dispatch({ type: "ADD_SWAGGER", payload: files });
}}
/>
</div>
<div className="mb-8 mt-4 flex items-center justify-between space-x-6">
<div>
<div className="mb-1 text-sm font-medium text-slate-800">
Important Instructions
</div>
<div className="text-xs">
<ul>
<li>
✅ Make sure each{" "}
<strong>endpoint have description and operation id</strong>,
results will be significantly better with a good description
</li>
<li>
✅ Make sure that the swagger file is valid, the system
might not be able to parse invalid files,{" "}
<Link href="https://editor.swagger.io/" target="_blank">
use this tool validate your schema
</Link>
</li>
<li>
✅ Do not add any Authorization layers, we will show you how
to authorize your own requests by yourself
</li>
<li>
✅ This is a *very* new product, so many things does not make
sense/work at this stage{" "}
</li>
</ul>
</div>
</div>
</div>
</div>
}


const formDialog = atom({
swagger: false,
manually: false
})


export function DefineActionsStep() {
const { nextStep, previousStep } = useWizard();
const [state, $importActionsFromSwagger] = useAsyncFn(importActionsFromSwagger)
const [addActionState, $addAction] = useAsyncFn(createActionByBotId)
const {
state: { swaggerFiles, createdCopilot },
dispatch,
} = useCreateCopilot();
const [dialogs, setDialogs] = useAtom(formDialog)
const { data: actions } = useSWR(createdCopilot ? (createdCopilot?.id + '/actions') : null, async () => createdCopilot?.id ? await getActionsByBotId(createdCopilot?.id) : null)
async function addActionFromSwagger() {
const swaggerFile = _.first(swaggerFiles);
if (swaggerFile && createdCopilot) {
const response = await $importActionsFromSwagger(createdCopilot.id, swaggerFile)
if (response.data) {
toast({
title: "Actions imported successfully",
description: "We have imported your actions successfully",
variant: "success",
});
}
// reset swagger files
dispatch({
type: "ADD_SWAGGER",
payload: [],
});
revalidateActions(createdCopilot.id)
setDialogs({
...dialogs,
swagger: false
})
}
}
return (
<div className="relative p-1">
<div className="mb-5 flex items-center justify-between">
<h2 className="text-3xl font-bold text-accent-foreground">
Define your actions ✨
</h2>
</div>

<p className="mb-2">
You copilot will use these APIs to communicate with your product and
execute actions
</p>
<div className="flex items-center mb-2 space-x-2 justify-end">
{/* Via Form */}
<AlertDialog open={dialogs.manually} onOpenChange={(open) => setDialogs({
...dialogs,
manually: open,
})}>
<AlertDialogContent>
<AlertDialogHeader className="flex items-center justify-between w-full flex-row">
<AlertDialogTitle className="flex-1 text-lg font-bold">
Define API action
</AlertDialogTitle>
</AlertDialogHeader>
<ActionForm
onSubmit={async (values) => {
if (createdCopilot) {
const { data } = await $addAction(createdCopilot.id, values);
if (data) {
toast({
title: "Action created successfully",
description: "We have created your action successfully",
variant: "success",
});
setDialogs({
...dialogs,
manually: false
})
revalidateActions(createdCopilot.id)
}
}
}}
footer={
() => <AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant='outline'>Cancel</Button>
</AlertDialogCancel>
<Button type="submit" loading={addActionState.loading}>
Create Action
</Button>
</AlertDialogFooter>
} />
</AlertDialogContent>
<AlertDialogTrigger asChild>
<Button className="space-x-1" size='xs' variant='secondary'>
<Plus className="w-4 h-4" />
<span>
Add action manually
</span>
</Button>
</AlertDialogTrigger>
</AlertDialog>
{/* Via swagger Def file */}
<AlertDialog open={dialogs.swagger} onOpenChange={(open) => setDialogs({
...dialogs,
swagger: open,
})}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex-1 text-lg font-bold">
Import from Swagger
</AlertDialogTitle>
</AlertDialogHeader>
<GetActionsFromSwagger />
<AlertDialogFooter>
<AlertDialogCancel asChild>
<Button variant='outline'>Cancel</Button>
</AlertDialogCancel>
<Button onClick={addActionFromSwagger} loading={state.loading}>Import</Button>
</AlertDialogFooter>
</AlertDialogContent>
<AlertDialogTrigger asChild >
<Button className="space-x-1" size='xs' variant='secondary'>
<UploadCloud className="w-4 h-4" />
<span>
Import actions from Swagger file
</span>
</Button>
</AlertDialogTrigger>
</AlertDialog>
</div>
<div className="flex items-start flex-col gap-2 overflow-auto max-h-60 px-2 py-1">
{
_.isEmpty(actions?.data) ?
<div className="mx-auto">
<EmptyBlock>
<div className="text-center text-sm">
<span className="block">
No actions added yet.
</span>
<span className="block">
You can add one manually or import a bunch from swagger file
</span>
</div>
</EmptyBlock>
</div>
:
_.map(actions?.data, (endpoint, index) => {
return <div key={index} className="w-full p-2 shrink-0 flex overflow-hidden max-w-full gap-4 items-center justify-between border border-border transition-colors rounded-lg">
<div className="flex-1 flex items-center justify-start overflow-hidden shrink-0">
<div className="flex items-center gap-5 overflow-hidden shrink-0">
<span className={cn(methodVariants({
method: endpoint.request_type
}))}>
{endpoint.request_type}
</span>
<p className="flex-1 line-clamp-1 overflow-ellipsis font-medium text-xs">
{endpoint.api_endpoint || endpoint.name}
</p>
</div>
</div>
<div className="space-x-2">
<button className="text-destructive">
<Trash2 className="w-4 h-4" onClick={() => confirm("are you sure")} />
</button>
</div>
</div>
})
}
</div>

<footer className="flex w-full items-center justify-between gap-5 pt-5">
<Button
variant="ghost"
onClick={previousStep}
className="flex items-center justify-center gap-1 underline"
>
Back
</Button>
{createdCopilot && (
<Button
variant='ghost'
className="flex items-center justify-center gap-1 underline"
onClick={nextStep}>
{
_.isEmpty(actions?.data) ? "Skip for now ►" : "Next"
}
</Button>
)}
</footer>
</div >
);
}
Loading

0 comments on commit 431cfdc

Please sign in to comment.