add copy json to copilot

This commit is contained in:
ramnique 2025-01-23 11:39:27 +05:30
parent 8c6c3405d8
commit 7bf32d6746
7 changed files with 81 additions and 50 deletions

View file

@ -1,5 +1,5 @@
'use client';
import { Spinner } from "@nextui-org/react";
import { Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Spinner } from "@nextui-org/react";
import { useEffect, useState, useMemo } from "react";
import { z } from "zod";
import { PlaygroundChat, SimulationData, Workflow } from "@/app/lib/types";
@ -8,6 +8,7 @@ import { Chat } from "./chat";
import { useSearchParams } from "next/navigation";
import { ActionButton, Pane } from "../workflow/pane";
import { apiV1 } from "rowboat-shared";
import { EllipsisVerticalIcon, MessageSquarePlusIcon, PlayIcon } from "lucide-react";
function SimulateLabel() {
return <span>Simulate<sup className="pl-1">beta</sup></span>;
@ -75,18 +76,14 @@ export function App({
return <Pane title={viewSimulationMenu ? <SimulateLabel /> : "Chat"} actions={[
<ActionButton
key="new-chat"
icon={<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 10.5h.01m-4.01 0h.01M8 10.5h.01M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1h-6.6a1 1 0 0 0-.69.275l-2.866 2.723A.5.5 0 0 1 8 18.635V17a1 1 0 0 0-1-1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z" />
</svg>}
icon={<MessageSquarePlusIcon size={16} />}
onClick={handleNewChatButtonClick}
>
New chat
</ActionButton>,
!viewSimulationMenu && <ActionButton
key="simulate"
icon={<svg className="w-4 h-4" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 18V6l8 6-8 6Z" />
</svg>}
icon={<PlayIcon size={16} />}
onClick={handleSimulateButtonClick}
>
Simulate

View file

@ -7,7 +7,7 @@ import { AgenticAPIChatRequest, convertToAgenticAPIChatMessages, convertWorkflow
import { ComposeBox } from "./compose-box";
import { Button } from "@nextui-org/react";
import { apiV1 } from "rowboat-shared";
import { CheckIcon, CopyIcon } from "lucide-react";
import { CopyAsJsonButton } from "./copy-as-json-button";
export function Chat({
chat,
@ -101,7 +101,7 @@ export function Chat({
prompts,
startAgent,
};
setLastAgenticRequest(request);
setLastAgenticRequest(null);
setLastAgenticResponse(null);
try {
@ -109,7 +109,8 @@ export function Chat({
if (ignore) {
return;
}
setLastAgenticResponse(response.rawAPIResponse);
setLastAgenticRequest(response.rawRequest);
setLastAgenticResponse(response.rawResponse);
setMessages([...messages, ...response.messages.map((message) => ({
...message,
version: 'v1' as const,
@ -250,36 +251,15 @@ export function Chat({
lastRequest: lastAgenticRequest,
lastResponse: lastAgenticResponse,
}, null, 2);
navigator.clipboard.writeText(jsonString)
.then(() => {
setShowCopySuccess(true);
setTimeout(() => {
setShowCopySuccess(false);
}, 1500);
})
.catch(err => {
console.error('Failed to copy chat to clipboard:', err);
});
};
navigator.clipboard.writeText(jsonString);
}
function handleSystemMessageChange(message: string) {
setSystemMessage(message);
}
return <div className="relative h-full flex flex-col gap-8 pt-8 overflow-auto">
<Button
size="sm"
variant="bordered"
isIconOnly
onClick={handleCopyChat}
className="absolute top-2 right-0"
>
{showCopySuccess ? (
<CheckIcon size={16} />
) : (
<CopyIcon size={16} />
)}
</Button>
<CopyAsJsonButton onCopy={handleCopyChat} />
<Messages
projectId={projectId}
messages={messages}

View file

@ -0,0 +1,28 @@
import { CheckIcon, CopyIcon } from "lucide-react";
import { useState } from "react";
export function CopyAsJsonButton({ onCopy }: { onCopy: () => void }) {
const [showCopySuccess, setShowCopySuccess] = useState(false);
const handleCopyChat = () => {
onCopy();
setShowCopySuccess(true);
setTimeout(() => {
setShowCopySuccess(false);
}, 500);
};
return <button
onClick={handleCopyChat}
className="absolute top-0 right-0 text-gray-300 hover:text-gray-700 flex items-center gap-1 group"
>
{showCopySuccess ? (
<CheckIcon size={16} />
) : (
<CopyIcon size={16} />
)}
<div className="text-xs hidden group-hover:block">
{showCopySuccess ? 'Copied' : 'Copy as JSON'}
</div>
</button>
}

View file

@ -9,6 +9,7 @@ import { Action } from "./copilot_actions";
import clsx from "clsx";
import { Action as WorkflowDispatch } from "./workflow_editor";
import MarkdownContent from "@/app/lib/components/markdown-content";
import { CopyAsJsonButton } from "../playground/copy-as-json-button";
const CopilotContext = createContext<{
@ -181,6 +182,8 @@ function App({
const messagesEndRef = useRef<HTMLDivElement>(null);
const [appliedChanges, setAppliedChanges] = useState<Record<string, boolean>>({});
const [discardContext, setDiscardContext] = useState(false);
const [lastRequest, setLastRequest] = useState<unknown | null>(null);
const [lastResponse, setLastResponse] = useState<unknown | null>(null);
// Cycle through loading messages until reaching the last one
useEffect(() => {
@ -328,7 +331,10 @@ function App({
setResponseError(null);
try {
const copilotMessage = await getCopilotResponse(
setLastRequest(null);
setLastResponse(null);
const response = await getCopilotResponse(
projectId,
messages,
workflow,
@ -337,7 +343,9 @@ function App({
if (ignore) {
return;
}
setMessages([...messages, copilotMessage]);
setLastRequest(response.rawRequest);
setLastResponse(response.rawResponse);
setMessages([...messages, response.message]);
} catch (err) {
if (!ignore) {
setResponseError(`Failed to get copilot response: ${err instanceof Error ? err.message : 'Unknown error'}`);
@ -369,14 +377,24 @@ function App({
};
}, [messages, projectId, responseError, workflow, effectiveContext]);
function handleCopyChat() {
const jsonString = JSON.stringify({
messages: messages,
lastRequest: lastRequest,
lastResponse: lastResponse,
}, null, 2);
navigator.clipboard.writeText(jsonString);
}
// scroll to bottom on new messages
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}, [messages, loadingResponse]);
return <div className="h-full flex flex-col">
return <div className="h-full flex flex-col relative">
<CopilotContext.Provider value={{ workflow, handleApplyChange, appliedChanges }}>
<div className="grow flex flex-col gap-2 overflow-auto px-1">
<CopyAsJsonButton onCopy={handleCopyChat} />
<div className="grow flex flex-col gap-2 overflow-auto px-1 mt-6">
{messages.map((m, index) => {
// Calculate if this assistant message is stale
const isStale = m.role === 'assistant' && messages.slice(index + 1).some(
@ -412,7 +430,7 @@ function App({
</div>
<div className="shrink-0">
{responseError && (
<div className="max-w-[768px] mx-auto mb-4 p-2 bg-red-50 border border-red-200 rounded-lg flex gap-2 justify-between items-center">
<div className="max-w-[768px] mx-auto mb-4 p-2 bg-red-50 border border-red-200 rounded-lg flex gap-2 justify-between items-center text-sm">
<p className="text-red-600">{responseError}</p>
<Button
size="sm"

View file

@ -4,6 +4,7 @@ import { Dropdown, DropdownItem, DropdownTrigger, DropdownMenu } from "@nextui-o
import { useRef, useEffect } from "react";
import { ActionButton, Pane } from "./pane";
import clsx from "clsx";
import { EllipsisVerticalIcon } from "lucide-react";
interface EntityListProps {
agents: z.infer<typeof WorkflowAgent>[];
@ -177,9 +178,7 @@ function AgentDropdown({
return (
<Dropdown>
<DropdownTrigger>
<svg className="w-4 h-4 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeWidth="3" d="M12 6h.01M12 12h.01M12 18h.01" />
</svg>
<EllipsisVerticalIcon size={16} />
</DropdownTrigger>
<DropdownMenu
disabledKeys={[
@ -219,9 +218,7 @@ function EntityDropdown({
return (
<Dropdown>
<DropdownTrigger>
<svg className="w-4 h-4 text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" strokeLinecap="round" strokeWidth="3" d="M12 6h.01M12 12h.01M12 18h.01" />
</svg>
<EllipsisVerticalIcon size={16} />
</DropdownTrigger>
<DropdownMenu
onAction={(key) => {

View file

@ -23,7 +23,7 @@ export function Pane({
{title}
</div>
{!actions && <div className="w-4 h-4" />}
{actions && <div className={clsx("rounded-md hover:text-gray-800 px-2 text-sm flex items-center gap-1", {
{actions && <div className={clsx("rounded-md hover:text-gray-800 px-2 text-sm flex items-center gap-2", {
"text-blue-600": fancy,
"text-gray-400": !fancy,
})}>