improve copilot response styling

This commit is contained in:
ramnique 2025-01-21 10:09:19 +05:30
parent 988dca96dc
commit 281ad7f2cd

View file

@ -5,6 +5,7 @@ import { z } from "zod";
import { Workflow, CopilotAssistantMessage, CopilotAssistantMessageActionPart } from "@/app/lib/types"; import { Workflow, CopilotAssistantMessage, CopilotAssistantMessageActionPart } from "@/app/lib/types";
import { PreviewModalProvider, usePreviewModal } from './preview-modal'; import { PreviewModalProvider, usePreviewModal } from './preview-modal';
import { getAppliedChangeKey } from "./copilot"; import { getAppliedChangeKey } from "./copilot";
import { CheckCheckIcon, CheckIcon, ChevronsDownIcon, ChevronsUpIcon, EyeIcon, PencilIcon, PlusIcon } from "lucide-react";
const ActionContext = createContext<{ const ActionContext = createContext<{
msgIndex: number; msgIndex: number;
@ -14,7 +15,7 @@ const ActionContext = createContext<{
handleApplyChange: (messageIndex: number, actionIndex: number, field?: string) => void; handleApplyChange: (messageIndex: number, actionIndex: number, field?: string) => void;
appliedFields: string[]; appliedFields: string[];
stale: boolean; stale: boolean;
}>({ msgIndex: 0, actionIndex: 0, action: null, workflow: null, handleApplyChange: () => {}, appliedFields: [], stale: false }); }>({ msgIndex: 0, actionIndex: 0, action: null, workflow: null, handleApplyChange: () => { }, appliedFields: [], stale: false });
export function Action({ export function Action({
msgIndex, msgIndex,
@ -33,42 +34,73 @@ export function Action({
appliedChanges: Record<string, boolean>; appliedChanges: Record<string, boolean>;
stale: boolean; stale: boolean;
}) { }) {
const [expanded, setExpanded] = useState(Object.entries(action.config_changes).length <= 2); const [expanded, setExpanded] = useState(false);
const changes = Object.entries(action.config_changes).slice(0, expanded ? undefined : 2);
// determine whether all changes contained in this action are applied // determine whether all changes contained in this action are applied
const appliedFields = Object.keys(action.config_changes).filter(key => appliedChanges[getAppliedChangeKey(msgIndex, actionIndex, key)]); const appliedFields = Object.keys(action.config_changes).filter(key => appliedChanges[getAppliedChangeKey(msgIndex, actionIndex, key)]);
console.log('appliedFields', appliedFields); console.log('appliedFields', appliedFields);
return <div className={clsx('flex flex-col rounded-sm border shadow-sm', { // determine whether all changes contained in this action are applied
'bg-blue-50 border-blue-200': action.action === 'create_new', const allApplied = Object.keys(action.config_changes).every(key => appliedFields.includes(key));
'bg-amber-50 border-amber-200': action.action === 'edit',
'bg-gray-50 border-gray-200': stale, // generate apply change function
const applyChangeHandler = () => {
handleApplyChange(msgIndex, actionIndex);
}
return <div className={clsx('flex flex-col rounded-sm border', {
'bg-gray-50 border-gray-800 shadow-sm': !stale && !allApplied,
'bg-gray-100 border-gray-200': stale || allApplied,
})}> })}>
<ActionContext.Provider value={{ msgIndex, actionIndex, action, workflow, handleApplyChange, appliedFields, stale }}> <ActionContext.Provider value={{ msgIndex, actionIndex, action, workflow, handleApplyChange, appliedFields, stale }}>
<ActionHeader /> <ActionHeader />
<PreviewModalProvider> <ActionSummary />
<ActionBody> {expanded && <PreviewModalProvider>
{changes.map(([key, value]) => { <div className="flex flex-col gap-2 px-1">
{Object.entries(action.config_changes).map(([key, value]) => {
return <ActionField key={key} field={key} /> return <ActionField key={key} field={key} />
})} })}
</ActionBody> </div>
</PreviewModalProvider> </PreviewModalProvider>}
{Object.entries(action.config_changes).length > 2 && <button className={clsx('flex rounded-b-sm flex-col items-center justify-center', { <div className="flex items-center">
'bg-blue-100 hover:bg-blue-200 text-blue-600': action.action === 'create_new', <button
'bg-amber-100 hover:bg-amber-200 text-amber-600': action.action === 'edit', className="grow rounded-l-sm bg-blue-100 text-blue-500 hover:bg-blue-200 disabled:bg-gray-100 disabled:text-green-500 flex flex-col items-center justify-center h-8"
'bg-gray-100 hover:bg-gray-200 text-gray-600': stale, onClick={applyChangeHandler}
})} onClick={() => setExpanded(!expanded)}> disabled={stale || allApplied}
{expanded ? ( >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-chevrons-up"><path d="m17 11-5-5-5 5" /><path d="m17 18-5-5-5 5" /></svg> <div className="flex items-center gap-2 justify-center">
) : ( <CheckCheckIcon size={16} />
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-chevrons-down"><path d="m7 6 5 5 5-5" /><path d="m7 13 5 5 5-5" /></svg> <div className="font-medium text-xs">{allApplied ? 'Applied' : 'Apply'}</div>
)} </div>
</button>} </button>
<button
className="w-10 shrink-0 flex flex-col items-center h-8 rounded-r-sm bg-gray-100 text-gray-600 hover:bg-gray-200 justify-center"
onClick={() => setExpanded(!expanded)}
>
<div className="flex items-center gap-2 justify-center text-gray-400">
{expanded ? (
<ChevronsUpIcon size={16} />
) : (
<ChevronsDownIcon size={16} />
)}
</div>
</button>
</div>
</ActionContext.Provider> </ActionContext.Provider>
</div>; </div>;
} }
export function ActionSummary() {
const { msgIndex, actionIndex, action, workflow, handleApplyChange, appliedFields, stale } = useContext(ActionContext);
if (!action || !workflow) return null;
return <div className="px-1 my-1">
<div className="bg-white rounded-sm p-2 text-sm">
{action.change_description}
</div>
</div>;
}
export function ActionHeader() { export function ActionHeader() {
const { msgIndex, actionIndex, action, workflow, handleApplyChange, appliedFields, stale } = useContext(ActionContext); const { msgIndex, actionIndex, action, workflow, handleApplyChange, appliedFields, stale } = useContext(ActionContext);
if (!action || !workflow) return null; if (!action || !workflow) return null;
@ -76,47 +108,13 @@ export function ActionHeader() {
const targetType = action.config_type === 'tool' ? 'tool' : action.config_type === 'agent' ? 'agent' : 'prompt'; const targetType = action.config_type === 'tool' ? 'tool' : action.config_type === 'agent' ? 'agent' : 'prompt';
const change = action.action === 'create_new' ? 'Create' : 'Edit'; const change = action.action === 'create_new' ? 'Create' : 'Edit';
// determine whether all changes contained in this action are applied return <div className="flex gap-2 items-center py-1 px-1">
const allApplied = Object.keys(action.config_changes).every(key => appliedFields.includes(key)); {action.action == 'create_new' && <PlusIcon size={16} />}
{action.action == 'edit' && <PencilIcon size={16} />}
// generate apply change function <div className="text-sm truncate">{`${change} ${targetType}`}: <span className="font-medium">{action.name}</span></div>
const applyChangeHandler = () => {
handleApplyChange(msgIndex, actionIndex);
}
return <div className={clsx('flex justify-between items-center px-2 py-1 rounded-t-sm', {
'bg-blue-100': action.action === 'create_new',
'bg-amber-100': action.action === 'edit',
'bg-gray-100': stale,
})}>
<div className={clsx('text-sm truncate', {
'text-blue-600': action.action === 'create_new',
'text-amber-600': action.action === 'edit',
'text-gray-600': stale,
})}>{`${change} ${targetType}`}: <span className="font-medium">{action.name}</span></div>
<button className={clsx('flex gap-1 items-center text-sm hover:text-black', {
'text-blue-600': action.action === 'create_new',
'text-amber-600': action.action === 'edit',
'text-green-600': allApplied,
'text-gray-600': stale,
})}
onClick={applyChangeHandler}
disabled={stale || allApplied}
>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-check-check"><path d="M18 6 7 17l-5-5" /><path d="m22 10-7.5 7.5L13 16" /></svg>
{!allApplied && <div className="font-medium">Apply</div>}
</button>
</div>; </div>;
} }
export function ActionBody({
children,
}: {
children: React.ReactNode;
}) {
return <div className="flex flex-col gap-2 p-2">{children}</div>;
}
export function ActionField({ export function ActionField({
field, field,
}: { }: {
@ -183,20 +181,13 @@ export function ActionField({
return <div className="flex flex-col bg-white rounded-sm"> return <div className="flex flex-col bg-white rounded-sm">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<div className={clsx('text-xs font-semibold px-2 py-1', { <div className="text-xs font-semibold px-2 py-1 text-gray-600">{field}</div>
'text-blue-600': action.action === 'create_new',
'text-amber-600': action.action === 'edit',
'text-gray-600': stale,
})}>{field}</div>
{previewCondition && <div className="flex gap-4 items-center bg-gray-50 rounded-bl-sm rounded-tr-sm px-2 py-1"> {previewCondition && <div className="flex gap-4 items-center bg-gray-50 rounded-bl-sm rounded-tr-sm px-2 py-1">
<button <button
className="text-gray-500 hover:text-black" className="text-gray-500 hover:text-black"
onClick={previewModalHandler} onClick={previewModalHandler}
> >
<svg className="w-[16px] h-[16px]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <EyeIcon size={16} />
<path stroke="currentColor" strokeWidth="1.5" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z" />
<path stroke="currentColor" strokeWidth="1.5" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button> </button>
{action.action === 'edit' && <button {action.action === 'edit' && <button
className={clsx("text-gray-500 hover:text-black", { className={clsx("text-gray-500 hover:text-black", {
@ -206,7 +197,7 @@ export function ActionField({
onClick={applyChangeHandler} onClick={applyChangeHandler}
disabled={stale || applied} disabled={stale || applied}
> >
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-check"><path d="M20 6 9 17l-5-5" /></svg> <CheckIcon size={16} />
</button>} </button>}
</div>} </div>}
</div> </div>