Skip to content

Commit

Permalink
feat(frontend): support clone chat engine and edit prompts
Browse files Browse the repository at this point in the history
  • Loading branch information
634750802 committed Jul 24, 2024
1 parent e7f9655 commit eb9ac3d
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 15 deletions.
28 changes: 28 additions & 0 deletions frontend/app/src/api/chat-engines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export type ChatEngineLLMOptions = {
text_qa_prompt: string;
refine_prompt: string;

intent_graph_knowledge: string
normal_graph_knowledge: string

provider: string;
reranker_provider: string;
reranker_top_k: number;
Expand All @@ -48,6 +51,8 @@ const llmOptionsSchema =
condense_question_prompt: z.string(),
text_qa_prompt: z.string(),
refine_prompt: z.string(),
intent_graph_knowledge: z.string(),
normal_graph_knowledge: z.string(),
provider: z.string(),
reranker_provider: z.string(),
reranker_top_k: z.number(),
Expand Down Expand Up @@ -95,3 +100,26 @@ export async function updateChatEngine (id: number, partial: Partial<Pick<ChatEn
})
.then(handleErrors);
}

export async function createChatEngine (create: Pick<ChatEngine, 'name' | 'llm_id' | 'fast_llm_id' | 'engine_options'>) {
return await fetch(BASE_URL + `/api/v1/admin/chat-engines`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...await opaqueCookieHeader(),
},
body: JSON.stringify(create),
})
.then(handleResponse(chatEngineSchema));
}


export async function deleteChatEngine (id: number): Promise<void> {
await fetch(BASE_URL + `/api/v1/admin/chat-engines/${id}`, {
method: 'DELETE',
headers: {
...await opaqueCookieHeader(),
},
})
.then(handleErrors);
}
67 changes: 66 additions & 1 deletion frontend/app/src/app/(main)/(admin)/chat-engines/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
'use client';

import { type ChatEngine, listChatEngines } from '@/api/chat-engines';
import { type ChatEngine, createChatEngine, deleteChatEngine, listChatEngines } from '@/api/chat-engines';
import { AdminPageHeading } from '@/components/admin-page-heading';
import { datetime } from '@/components/cells/datetime';
import { DangerousActionButton } from '@/components/dangerous-action-button';
import { DataTableRemote } from '@/components/data-table-remote';
import { Button } from '@/components/ui/button';
import { useDataTable } from '@/components/use-data-table';
import type { CellContext, ColumnDef } from '@tanstack/react-table';
import { createColumnHelper } from '@tanstack/table-core';
import { CopyIcon, TrashIcon } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
import { toast } from 'sonner';

const helper = createColumnHelper<ChatEngine>();

Expand All @@ -18,6 +25,15 @@ const columns = [
helper.accessor('created_at', { cell: datetime }),
helper.accessor('updated_at', { cell: datetime }),
helper.accessor('is_default', { cell: (ctx) => ctx.getValue() ? 'Yes' : '' }),
helper.display({
header: 'Actions',
cell: ({ row }) => (
<span className="flex gap-2 items-center">
<CloneButton chatEngine={row.original} />
<DeleteButton chatEngine={row.original} />
</span>
),
}),
// {
// id: 'actions',
// cell: ({ row }) => <DataTableRowActions row={row} />,
Expand All @@ -44,3 +60,52 @@ export default function ChatEnginesPage () {
);
}

function CloneButton ({ chatEngine }: { chatEngine: ChatEngine }) {
const [cloning, setCloning] = useState(false);
const [transitioning, startTransition] = useTransition();
const router = useRouter();

return (
<Button
variant="ghost"
className="text-xs"
disabled={cloning || transitioning}
size="sm"
onClick={() => {
setCloning(true);
const { name, llm_id, fast_llm_id, engine_options } = chatEngine;
createChatEngine({
name: `${name} Copy`, llm_id, fast_llm_id, engine_options,
})
.then(newEngine => {
toast('Chat Engine successfully cloned.');
router.push(`/chat-engines/${newEngine.id}`);
})
.finally(() => {
setCloning(false);
});
}}
>
<CopyIcon className="w-3 mr-1" />
Clone
</Button>
);
}

function DeleteButton ({ chatEngine }: { chatEngine: ChatEngine }) {
const { reload } = useDataTable();

return (
<DangerousActionButton
action={async () => {
await deleteChatEngine(chatEngine.id);
reload?.();
}}
variant="ghost"
className="text-xs text-destructive hover:text-destructive hover:bg-destructive/20"
>
<TrashIcon className="w-3 mr-1" />
Delete
</DangerousActionButton>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ChatEngineDetails ({ chatEngine }: { chatEngine: ChatEngine }) {
<OptionDetail title="Is default" value={chatEngine.is_default ? 'Yes' : 'No'} editPanel={<EditIsDefaultForm chatEngine={chatEngine} />} />
</div>
<Separator />
<ChatEngineOptionsDetails options={chatEngine.engine_options} />
<ChatEngineOptionsDetails options={chatEngine.engine_options} editable={chatEngine} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import type { ChatEngineOptions } from '@/api/chat-engines';
import type { ChatEngine, ChatEngineOptions } from '@/api/chat-engines';
import { ChatEngineKnowledgeGraphDetails } from '@/components/chat-engine/knowledge-graph-details';
import { ChatEngineLLMDetails } from '@/components/chat-engine/llm-details';
import { Separator } from '@/components/ui/separator';

export function ChatEngineOptionsDetails ({ detailed = true, options }: { detailed?: boolean, options: ChatEngineOptions }) {
export function ChatEngineOptionsDetails ({
detailed = true,
editable,
options,
}: {
detailed?: boolean
editable?: ChatEngine
options: ChatEngineOptions
}) {
return (
<>
<section className="space-y-2">
Expand All @@ -16,7 +24,7 @@ export function ChatEngineOptionsDetails ({ detailed = true, options }: { detail
<section className="space-y-2">
<div className="text-base font-medium">LLM</div>
<div className="space-y-2 text-sm">
<ChatEngineLLMDetails options={options.llm} />
<ChatEngineLLMDetails editable={editable} options={options.llm} />
</div>
</section>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use client';

import { type ChatEngine, updateChatEngine } from '@/api/chat-engines';
import { useManagedDialog } from '@/components/managed-dialog';
import { Button } from '@/components/ui/button';
import { DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { useTransition } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';

const schema = z.object({
prompt: z.string().min(1),
});

export interface EditOptionsLlmPromptFormProps {
chatEngine: ChatEngine;
type: 'intent_graph_knowledge'
| 'normal_graph_knowledge'
| 'condense_question_prompt'
| 'refine_prompt'
| 'text_qa_prompt';
}

export function EditOptionsLlmPromptForm ({ chatEngine, type }: EditOptionsLlmPromptFormProps) {
const router = useRouter();
const [transitioning, startTransition] = useTransition();
const { setOpen } = useManagedDialog();

const form = useForm<{ prompt: string }>({
resolver: zodResolver(schema),
defaultValues: {
prompt: chatEngine.engine_options.llm[type],
},
});

const handleSubmit = form.handleSubmit(async (data) => {
const chatEngineOptions = {
...chatEngine.engine_options,
llm: {
...chatEngine.engine_options.llm,
[type]: data.prompt,
},
};
await updateChatEngine(chatEngine.id, { engine_options: chatEngineOptions });
startTransition(() => {
router.refresh();
});
toast(`ChatEngine's ${type} successfully updated.`);
setOpen(false);
});

return (
<>
<DialogHeader>
<DialogTitle>Update chat engine&#39;s {type}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form id="update-form" className="space-y-4" onSubmit={handleSubmit}>
<FormField
name="prompt"
render={({ field }) => (
<FormItem>
<FormLabel>{type}</FormLabel>
<FormControl>
<Textarea className='min-h-[50vh]' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
<DialogFooter>
<Button type="submit" form="update-form" disabled={form.formState.disabled || form.formState.isSubmitting || transitioning}>
Update
</Button>
</DialogFooter>
</>
);
}
15 changes: 9 additions & 6 deletions frontend/app/src/components/chat-engine/llm-details.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import type { ChatEngineLLMOptions } from '@/api/chat-engines';
import { OptionDetail } from '@/components/option-detail';
import type { ChatEngine, ChatEngineLLMOptions } from '@/api/chat-engines';
import { EditOptionsLlmPromptForm } from '@/components/chat-engine/edit-options-llm-prompt-form';
import { PromptViewer } from '@/components/chat-engine/prompt-viewer';
import { OptionDetail } from '@/components/option-detail';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';

export function ChatEngineLLMDetails ({ options }: { options: ChatEngineLLMOptions }) {
export function ChatEngineLLMDetails ({ editable, options }: { editable?: ChatEngine, options: ChatEngineLLMOptions }) {
return (
<div className="space-y-2 text-sm">
<OptionDetail title="Provider" value={options.provider} />
<OptionDetail title="Model" value={options[`${options.provider}_chat_model` as never]} />
<OptionDetail title="Reranker Provider" value={options.reranker_provider} />
<OptionDetail title="Reranker Top K" value={options.reranker_top_k} />
{options.condense_question_prompt && <OptionDetail title="Condense question prompt" value={<PromptPreviewDialog title="Condense question prompt" value={options.condense_question_prompt} />} />}
{options.refine_prompt && <OptionDetail title="Refine prompt" value={<PromptPreviewDialog title="Refine prompt" value={options.refine_prompt} />} />}
{options.text_qa_prompt && <OptionDetail title="Text QA prompt" value={<PromptPreviewDialog title="Text QA prompt" value={options.text_qa_prompt} />} />}
{options.condense_question_prompt && <OptionDetail title="Condense question prompt" value={<PromptPreviewDialog title="Condense question prompt" value={options.condense_question_prompt} />} editPanel={editable && <EditOptionsLlmPromptForm chatEngine={editable} type="condense_question_prompt" />} />}
{options.refine_prompt && <OptionDetail title="Refine prompt" value={<PromptPreviewDialog title="Refine prompt" value={options.refine_prompt} />} editPanel={editable && <EditOptionsLlmPromptForm chatEngine={editable} type="refine_prompt" />} />}
{options.text_qa_prompt && <OptionDetail title="Text QA prompt" value={<PromptPreviewDialog title="Text QA prompt" value={options.text_qa_prompt} />} editPanel={editable && <EditOptionsLlmPromptForm chatEngine={editable} type="text_qa_prompt" />} />}
{options.intent_graph_knowledge && <OptionDetail title="Intent Graph Knowledge prompt" value={<PromptPreviewDialog title="Intent Graph Knowledge prompt" value={options.intent_graph_knowledge} />} editPanel={editable && <EditOptionsLlmPromptForm chatEngine={editable} type="intent_graph_knowledge" />} />}
{options.normal_graph_knowledge && <OptionDetail title="Normal Graph Knowledge prompt" value={<PromptPreviewDialog title="Normal Graph Knowledge prompt" value={options.normal_graph_knowledge} />} editPanel={editable && <EditOptionsLlmPromptForm chatEngine={editable} type="normal_graph_knowledge" />} />}
</div>
);
}
Expand Down
6 changes: 3 additions & 3 deletions frontend/app/src/components/data-table-remote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { type ReactNode, useEffect, useMemo, useState } from 'react';
import useSWR from 'swr';

export interface PageApiOptions {
globalFilter: string
globalFilter: string;
}

interface DataTableRemoteProps<TData, TValue> {
Expand Down Expand Up @@ -66,7 +66,7 @@ export function DataTableRemote<TData, TValue> ({
revalidateOnFocus: false,
focusThrottleInterval: 1000,
keepPreviousData: true,
onError: console.error
onError: console.error,
});

useEffect(() => {
Expand Down Expand Up @@ -135,7 +135,7 @@ export function DataTableRemote<TData, TValue> ({
});

return (
<DataTableProvider value={table}>
<DataTableProvider value={{ ...table, reload: () => { mutate(); } }}>
{before}
{toolbar ? toolbar(table) : null}
<TooltipProvider>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/components/use-data-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import type { Table } from '@tanstack/table-core';
import { createContext, useContext } from 'react';

const DataTableContext = createContext<Table<any> | null>(null);
const DataTableContext = createContext<Table<any> & { reload?: () => void } | null>(null);

export const DataTableProvider = DataTableContext.Provider;

Expand Down

0 comments on commit eb9ac3d

Please sign in to comment.