From 197d00f07459e97c2d716cff13596da1e43083cd Mon Sep 17 00:00:00 2001 From: Cesar Date: Wed, 20 Dec 2023 12:11:56 +0100 Subject: [PATCH 01/12] feat: chat details modal init --- apps/browser/components/Chat.tsx | 6 +- apps/browser/components/ChatLogs.tsx | 216 ++++++++++++++---- .../browser/components/modals/ChatDetails.tsx | 44 ++++ apps/browser/lib/hooks/useEvoService.ts | 4 +- apps/browser/lib/services/evo/EvoThread.ts | 31 +-- .../lib/services/evo/createEvoInstance.ts | 2 + apps/browser/lib/sys/logger/BrowserLogger.ts | 3 +- .../agent-core/agent/basicFunctionCallLoop.ts | 2 + packages/agents/src/agents/Evo/index.ts | 3 +- 9 files changed, 238 insertions(+), 73 deletions(-) create mode 100644 apps/browser/components/modals/ChatDetails.tsx diff --git a/apps/browser/components/Chat.tsx b/apps/browser/components/Chat.tsx index f3763d00..9fd74cec 100644 --- a/apps/browser/components/Chat.tsx +++ b/apps/browser/components/Chat.tsx @@ -43,13 +43,15 @@ const Chat: React.FC = ({ const [localOpenAiApiKey] = useAtom(localOpenAiApiKeyAtom); const [, setAccountModalOpen] = useAtom(showAccountModalAtom); const [welcomeModalSeen] = useAtom(welcomeModalAtom); + const [currentStatus, setCurrentStatus] = useState(); const [message, setMessage] = useState(""); const [goalSent, setGoalSent] = useState(false); const { logs, isStarting, isRunning, handleStart } = useEvoService( chatId, isAuthenticated, - onCreateChat + onCreateChat, + (status: string) => setCurrentStatus(status) ); const shouldShowExamplePrompts = !logs || logs.length === 0; @@ -95,7 +97,7 @@ const Chat: React.FC = ({ {shouldShowExamplePrompts && !goalSent ? ( ) : ( - + )}
; +}; + +export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { + if (!messages || !messages.length) return []; + const dividedMessages: MessageSet[] = []; + let currentMessageSet: MessageSet = { userMessage: "", details: {} }; + let currentStepTitle = ""; + let evoMessageFlag = false; + + console.log(messages) + + messages.forEach((message, index) => { + if (!message.title.startsWith("#")) { + if (currentMessageSet.userMessage && !evoMessageFlag && message.user === "evo") { + // This is the evoMessage after details + currentMessageSet.evoMessage = message.title; + evoMessageFlag = true; + } else if (!currentMessageSet.userMessage && message.user === "user") { + // This is the initial userMessage + currentMessageSet.userMessage = message.title; + evoMessageFlag = false; + } else { + // New set starts here + dividedMessages.push(currentMessageSet); + currentMessageSet = { userMessage: message.title, details: {} }; + currentStepTitle = ""; + evoMessageFlag = false; + } + } else { + if (message.title.startsWith("## ")) { + // New step title + currentStepTitle = message.title; + currentMessageSet.details[currentStepTitle] = []; + } else if (currentStepTitle) { + // Add detail to the current step + currentMessageSet.details[currentStepTitle].push(message.title); + } + } + + // Handle the last element + if (index === messages.length - 1) { + dividedMessages.push(currentMessageSet); + } + }); + + return dividedMessages; +} + +export default function ChatLogs({ + logs, + isRunning, + currentStatus, +}: ChatLogsProps) { const listContainerRef = useRef(null); const [isAtBottom, setIsAtBottom] = useState(true); const { data: session } = useSession(); @@ -23,6 +86,12 @@ export default function ChatLogs(props: ChatLogsProps) { }); }; + const sanitizedLogs = useMemo(() => { + return sanitizeLogs(logs); + }, [logs]); + + const [logsDetails, setLogsDetails] = useState({ open: false, index: 0 }); + const handleScroll = useCallback(() => { // Detect if the user is at the bottom of the list const container = listContainerRef.current; @@ -55,10 +124,7 @@ export default function ChatLogs(props: ChatLogsProps) { }; }, [handleScroll]); - if (isAtBottom) { - scrollToBottom(); - } - + console.log(sanitizedLogs) return ( <>
@@ -69,58 +135,116 @@ export default function ChatLogs(props: ChatLogsProps) { onScroll={handleScroll} className="w-full flex-1 items-center space-y-6 overflow-y-auto overflow-x-clip px-2 py-3 text-left" > - {logs.map((msg, index, logs) => { - const isEvo = msg.user === "evo"; - const previousMessage = logs[index - 1]; + {sanitizedLogs.map((msg, index) => { return (
-
-
- {isEvo && previousMessage?.user === "user" ? ( - - ) : !isEvo ? ( - <> - {session?.user.image && session?.user.email ? ( - - ) : !session?.user.email ? ( - - ) : ( -
- )} - - ) : null} -
-
- {index === 0 || previousMessage?.user !== msg.user ? ( -
- {session?.user.name && !msg.user.includes("evo") - ? session?.user.name - : msg.user.charAt(0).toUpperCase() + msg.user.slice(1)} +
+ <> + {session?.user.image && session?.user.email ? ( + + ) : ( +
+ )} + +
+ <> +
+ + {session?.user.name ? session?.user.name : "User"} +
- ) : null} +
- {msg.title.toString()} - - {msg.content?.toString() ?? ""} - + {msg.userMessage}
- {isEvo && isRunning && index === logs.length - 1 && ( -
- -
- )} +
+ {/*
+ <> + + + +
*/} +
+
+
+ +
+ <> +
+ Evo + + setLogsDetails({ + open: true, + index, + }) + } + > + Details + +
+ + {msg.evoMessage && ( +
+ {msg.evoMessage} +
+ )} + {!msg.evoMessage && isRunning && ( + <> +
+ +
+
+ {currentStatus} +
+ {/* */} +
+
+ + )} +
+ {/*
+ <> + + + + +
*/}
); })}
+ setLogsDetails({ open: false, index: 0 })} + logs={sanitizedLogs[logsDetails.index]} + /> ); } diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx new file mode 100644 index 00000000..6f9399e4 --- /dev/null +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import Modal from "./ModalBase"; +import ReactMarkdown from "react-markdown"; +import { MessageSet } from "../ChatLogs"; + +interface ChatDetailsProps { + isOpen: boolean; + onClose: () => void; + logs: MessageSet; +} + +export default function ChatDetails(props: ChatDetailsProps) { + const [expandedStep, setExpandedStep] = useState(null); + + const toggleStep = (step: string) => { + if (expandedStep === step) { + setExpandedStep(null); + } else { + setExpandedStep(step); + } + }; + + return ( + +
+ {props.logs && + Object.entries(props.logs.details).map(([stepTitle, stepDetails]) => ( +
+ + {expandedStep === stepTitle && ( +
+ {stepDetails.map((detail, detailIndex) => ( + {detail} + ))} +
+ )} +
+ ))} +
+
+ ); +} diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index cb955322..55478d89 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -27,7 +27,8 @@ import { useUpdateChatTitle } from "../mutations/useUpdateChatTitle"; export const useEvoService = ( chatId: string | "" | undefined, isAuthenticated: boolean, - onCreateChat: (chatId: string) => void + onCreateChat: (chatId: string) => void, + handleStatusUpdate: (status: string) => void ): { logs: ChatLog[] | undefined; isStarting: boolean; @@ -91,6 +92,7 @@ export const useEvoService = ( loadChatLog, loadWorkspace, onChatLogAdded: handleChatLogAdded, + onStatusUpdate: handleStatusUpdate, onMessagesAdded: handleMessagesAdded, onVariableSet: handleVariableSet }; diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 4d3d7005..27247668 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -14,6 +14,7 @@ export interface EvoThreadConfig { loadChatLog: (chatId: string) => Promise; loadWorkspace: (chatId: string) => Promise; onChatLogAdded: (chatLog: ChatLog) => Promise; + onStatusUpdate: (status: string) => void; onMessagesAdded: (type: ChatLogType, messages: ChatMessage[]) => Promise; onVariableSet: (key: string, value: string) => Promise; } @@ -135,13 +136,11 @@ export class EvoThread { options.openAiApiKey, this._config.onMessagesAdded, this._config.onVariableSet, - (chatLog) => - this.onChatLog(chatLog) || Promise.resolve(), - () => - this._callbacks?.onGoalCapReached(), + (chatLog) => this.onChatLog(chatLog) || Promise.resolve(), + (status) => this._config.onStatusUpdate(status), + () => this._callbacks?.onGoalCapReached(), // onError - (error) => - this._callbacks?.onError(error) + (error) => this._callbacks?.onError(error) ); if (!evo) { @@ -209,32 +208,20 @@ export class EvoThread { let stepCounter = 1; while (this._state.isRunning) { + await this.onChatLog({ + title: `## Step ${stepCounter}`, + user: "evo", + }); const response = await iterator.next(); this._callbacks?.setWorkspace(this._state.workspace); if (response.done) { - const actionTitle = response.value.value.title; - if ( - actionTitle.includes("onGoalAchieved") || - actionTitle === "SUCCESS" - ) { - await this.onChatLog({ - title: "## Goal Achieved", - user: "evo", - }); - } - this.setIsRunning(false); evo?.reset(); break; } - await this.onChatLog({ - title: `## Step ${stepCounter}`, - user: "evo", - }); - if (!response.done) { const evoMessage = { title: `### Action executed:\n${response.value.title}`, diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index c1dd2c0a..1ed85f11 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -30,6 +30,7 @@ export function createEvoInstance( onMessagesAdded: (type: ChatLogType, messages: ChatMessage[]) => Promise, onVariableSet: (key: string, value: string) => Promise, onChatLog: (chatLog: ChatLog) => Promise, + onStatusUpdate: (status: string) => void, onGoalCapReached: () => void, onError: (error: string) => void ): Evo | undefined { @@ -41,6 +42,7 @@ export function createEvoInstance( title: message, }); }, + onStatusUpdate }); const logger = new Logger([browserLogger, new ConsoleLogger()], { promptUser: () => Promise.resolve("N/A"), diff --git a/apps/browser/lib/sys/logger/BrowserLogger.ts b/apps/browser/lib/sys/logger/BrowserLogger.ts index e78ba681..3bc6d9dc 100644 --- a/apps/browser/lib/sys/logger/BrowserLogger.ts +++ b/apps/browser/lib/sys/logger/BrowserLogger.ts @@ -2,6 +2,7 @@ import { ILogger } from "@evo-ninja/agent-utils"; export interface BrowserLoggerConfig { onLog(markdown: string): Promise; + onStatusUpdate(status: string): void; } export class BrowserLogger implements ILogger { @@ -12,7 +13,7 @@ export class BrowserLogger implements ILogger { } async notice(msg: string): Promise { - await this._config.onLog(msg); + await this._config.onStatusUpdate(msg); } async success(msg: string): Promise { diff --git a/packages/agents/src/agent-core/agent/basicFunctionCallLoop.ts b/packages/agents/src/agent-core/agent/basicFunctionCallLoop.ts index b70f2c73..17acbdb2 100644 --- a/packages/agents/src/agent-core/agent/basicFunctionCallLoop.ts +++ b/packages/agents/src/agent-core/agent/basicFunctionCallLoop.ts @@ -27,6 +27,7 @@ export async function* basicFunctionCallLoop( return ResultOk(finalOutput); } + await context.logger.notice("Analyzing which function should be called...") const response = await llm.getResponse(logs, agentFunctions); if (!response) { @@ -45,6 +46,7 @@ export async function* basicFunctionCallLoop( continue; } + await context.logger.notice("Executing function: " + sanitizedFunctionAndArgs.value[1].definition.name) const { result, functionCalled } = await executeAgentFunction(sanitizedFunctionAndArgs.value, fnArgs, context) // Save large results as variables diff --git a/packages/agents/src/agents/Evo/index.ts b/packages/agents/src/agents/Evo/index.ts index 098369a6..53799e6e 100644 --- a/packages/agents/src/agents/Evo/index.ts +++ b/packages/agents/src/agents/Evo/index.ts @@ -76,6 +76,7 @@ export class Evo extends Agent { const { chat } = this.context; const { messages } = chat.chatLogs; + await this.context.logger.notice("Predicting best next step...") const prediction = !this.previousPrediction || this.loopCounter % 2 === 0 ? await this.predictBestNextStep( @@ -110,7 +111,7 @@ export class Evo extends Agent { const predictionVector = await this.createEmbeddingVector(prediction); await this.context.logger.info("### Prediction:\n-> " + prediction); - + await this.context.logger.notice("Finding best agent to execute step...") const [agent, agentFunctions, persona, allFunctions] = await findBestAgent( predictionVector, this.context From 0853bcfd0e6d236fcdbd6c8e1606ff9bb38bfbde Mon Sep 17 00:00:00 2001 From: Cesar Date: Wed, 20 Dec 2023 22:35:02 +0100 Subject: [PATCH 02/12] feat: evo returns message as expected & status updated in evo service --- apps/browser/app/page.tsx | 9 +- apps/browser/components/ChatLogs.tsx | 82 ++++++++----------- .../browser/components/modals/ChatDetails.tsx | 37 +++++---- apps/browser/lib/services/evo/EvoThread.ts | 2 +- .../src/scripts/agent-plugin/index.ts | 4 +- .../agents/src/functions/OnGoalAchieved.ts | 4 +- packages/agents/src/functions/OnGoalFailed.ts | 4 +- 7 files changed, 66 insertions(+), 76 deletions(-) diff --git a/apps/browser/app/page.tsx b/apps/browser/app/page.tsx index 92c9336d..1d028d5e 100644 --- a/apps/browser/app/page.tsx +++ b/apps/browser/app/page.tsx @@ -44,10 +44,11 @@ function Dojo({ params }: { params: { id?: string } }) { const { mutateAsync: createChat } = useCreateChat(); const { mutateAsync: updateChatTitle } = useUpdateChatTitle(); - const { logs, isConnected, isStarting, isRunning, handleStart } = - useEvoService(chatId, isAuthenticated, (status: string) => - setCurrentStatus(status) - ); + const { logs, isConnected, isStarting, isRunning, handleStart } = useEvoService( + chatId, + isAuthenticated, + setCurrentStatus + ); const workspaceUploadUpdate = useWorkspaceUploadUpdate(); diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index ace46879..16dddb09 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -32,11 +32,13 @@ export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { let currentStepTitle = ""; let evoMessageFlag = false; - console.log(messages) - messages.forEach((message, index) => { if (!message.title.startsWith("#")) { - if (currentMessageSet.userMessage && !evoMessageFlag && message.user === "evo") { + if ( + currentMessageSet.userMessage && + !evoMessageFlag && + message.user === "evo" + ) { // This is the evoMessage after details currentMessageSet.evoMessage = message.title; evoMessageFlag = true; @@ -90,7 +92,10 @@ export default function ChatLogs({ return sanitizeLogs(logs); }, [logs]); - const [logsDetails, setLogsDetails] = useState({ open: false, index: 0 }); + const [logsDetails, setLogsDetails] = useState<{ + open: boolean; + index: number; + }>({ open: false, index: 0 }); const handleScroll = useCallback(() => { // Detect if the user is at the bottom of the list @@ -164,16 +169,6 @@ export default function ChatLogs({ {msg.userMessage}
- {/*
- <> - - - -
*/}
Evo - - setLogsDetails({ - open: true, - index, - }) - } - > - Details - + {msg.evoMessage && ( + + setLogsDetails({ + open: true, + index, + }) + } + > + Details + + )}
{msg.evoMessage && ( -
+ {msg.evoMessage} -
+ )} - {!msg.evoMessage && isRunning && ( + {!msg.evoMessage && isRunning && sanitizedLogs.length - 1 === index && ( <>
-
+
+ setLogsDetails({ + open: true, + index, + }) + } + > {currentStatus}
- {/* */}
)}
- {/*
- <> - - - - -
*/}
@@ -242,7 +228,7 @@ export default function ChatLogs({
setLogsDetails({ open: false, index: 0 })} + onClose={() => setLogsDetails({ ...logsDetails, open: false })} logs={sanitizedLogs[logsDetails.index]} /> diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 6f9399e4..8c18289f 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -9,7 +9,11 @@ interface ChatDetailsProps { logs: MessageSet; } -export default function ChatDetails(props: ChatDetailsProps) { +export default function ChatDetails({ + isOpen, + onClose, + logs, +}: ChatDetailsProps) { const [expandedStep, setExpandedStep] = useState(null); const toggleStep = (step: string) => { @@ -21,23 +25,22 @@ export default function ChatDetails(props: ChatDetailsProps) { }; return ( - +
- {props.logs && - Object.entries(props.logs.details).map(([stepTitle, stepDetails]) => ( -
- - {expandedStep === stepTitle && ( -
- {stepDetails.map((detail, detailIndex) => ( - {detail} - ))} -
- )} -
- ))} + {Object.entries(logs.details).map(([stepTitle, stepDetails]) => ( +
+ + {expandedStep === stepTitle && ( +
+ {stepDetails.map((detail, detailIndex) => ( + {detail} + ))} +
+ )} +
+ ))}
); diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 2719725d..e7c0fb10 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -140,7 +140,7 @@ export class EvoThread { options.openAiApiKey, this._config.onMessagesAdded, this._config.onVariableSet, - (chatLog) => this.onChatLog(chatLog) || Promise.resolve(), + (chatLog) => this.onChatLog(chatLog), (status) => this._config.onStatusUpdate(status), () => this._callbacks?.onGoalCapReached(), // onError diff --git a/packages/agent-utils/src/scripts/agent-plugin/index.ts b/packages/agent-utils/src/scripts/agent-plugin/index.ts index 72d36f95..a220e7a8 100644 --- a/packages/agent-utils/src/scripts/agent-plugin/index.ts +++ b/packages/agent-utils/src/scripts/agent-plugin/index.ts @@ -33,12 +33,12 @@ export class AgentPlugin extends Module { } public async onGoalAchieved(args: Args_onGoalAchieved): Promise { - await this._logger.success(`Goal has been achieved: ${args.message}`); + await this._logger.success(args.message); return true; } public async onGoalFailed(args: Args_onGoalFailed): Promise { - await this._logger.error(`Goal could not be achieved: ${args.message}`); + await this._logger.error(args.message); return true; } } diff --git a/packages/agents/src/functions/OnGoalAchieved.ts b/packages/agents/src/functions/OnGoalAchieved.ts index 827f5490..b845e058 100644 --- a/packages/agents/src/functions/OnGoalAchieved.ts +++ b/packages/agents/src/functions/OnGoalAchieved.ts @@ -8,13 +8,13 @@ interface OnGoalAchievedFuncParameters { export class OnGoalAchievedFunction extends ScriptFunction { name: string = "agent_onGoalAchieved"; - description: string = "Informs the user that the goal has been achieved."; + description: string = "Informs the user that the goal has been achieved. Return a complete answer for user's question"; parameters: any = { type: "object", properties: { message: { type: "string", - description: "information about how the goal was achieved", + description: "Complete answer for user's question", }, }, required: ["message"], diff --git a/packages/agents/src/functions/OnGoalFailed.ts b/packages/agents/src/functions/OnGoalFailed.ts index f9fbd0c9..af98cdb5 100644 --- a/packages/agents/src/functions/OnGoalFailed.ts +++ b/packages/agents/src/functions/OnGoalFailed.ts @@ -8,13 +8,13 @@ interface OnGoalFailedFuncParameters { export class OnGoalFailedFunction extends ScriptFunction<{}> { name: string = "agent_onGoalFailed"; - description: string = `Informs the user that the agent could not achieve the goal.`; + description: string = `Informs the user that the agent could not achieve the goal. Returns an explanation of why the goal could not be achieved`; parameters: any = { type: "object", properties: { message: { type: "string", - description: "information about how the goal was achieved", + description: "Explanation of why the goal could not be achieved", }, }, required: ["message"], From 0baab17bed8778f03365f022d4d1c2d4ccb35b3a Mon Sep 17 00:00:00 2001 From: Cesar Date: Wed, 20 Dec 2023 22:37:39 +0100 Subject: [PATCH 03/12] chore: show details button only when its not running & has evo message --- apps/browser/components/ChatLogs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 16dddb09..34274928 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -180,7 +180,7 @@ export default function ChatLogs({ <>
Evo - {msg.evoMessage && ( + {msg.evoMessage && !isRunning && ( setLogsDetails({ From 48680281525ea4255ab55504c39a58627e009c16 Mon Sep 17 00:00:00 2001 From: Colin Spence Date: Wed, 20 Dec 2023 22:26:57 -0700 Subject: [PATCH 04/12] Style updates to ChatLogs and ChatDetails --- apps/browser/components/ChatLogs.tsx | 49 +++++++------ .../browser/components/modals/ChatDetails.tsx | 70 +++++++++++++++---- apps/browser/styles/globals.css | 4 ++ 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 34274928..32339835 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -8,6 +8,7 @@ import React, { } from "react"; import ReactMarkdown from "react-markdown"; import LoadingCircle from "./LoadingCircle"; +import { ArrowSquareRight } from "@phosphor-icons/react"; import Avatar from "./Avatar"; import Logo from "./Logo"; import { useSession } from "next-auth/react"; @@ -181,7 +182,8 @@ export default function ChatLogs({
Evo {msg.evoMessage && !isRunning && ( - setLogsDetails({ open: true, @@ -189,8 +191,11 @@ export default function ChatLogs({ }) } > - Details - +
+ View Details +
+ + )}
@@ -199,26 +204,28 @@ export default function ChatLogs({ {msg.evoMessage} )} - {!msg.evoMessage && isRunning && sanitizedLogs.length - 1 === index && ( - <> -
- -
-
- setLogsDetails({ - open: true, - index, - }) - } - > - {currentStatus} + {!msg.evoMessage && + isRunning && + sanitizedLogs.length - 1 === index && ( + <> +
+ +
+
+ setLogsDetails({ + open: true, + index, + }) + } + > + {currentStatus} +
-
- - )} + + )}
diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 8c18289f..3015cea8 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -1,7 +1,10 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import Modal from "./ModalBase"; import ReactMarkdown from "react-markdown"; import { MessageSet } from "../ChatLogs"; +import { CaretUp } from "@phosphor-icons/react"; +import clsx from "clsx"; +import { Transition } from "@headlessui/react"; interface ChatDetailsProps { isOpen: boolean; @@ -15,30 +18,73 @@ export default function ChatDetails({ logs, }: ChatDetailsProps) { const [expandedStep, setExpandedStep] = useState(null); + const [heights, setHeights] = useState<{ [index: number]: number }>({}); + const contentRefs = useRef<{ [index: number]: HTMLDivElement | null }>({}); - const toggleStep = (step: string) => { + useEffect(() => { + const newHeights = []; + Object.keys(contentRefs.current).forEach((key) => { + const index = parseInt(key); + const el = contentRefs.current[index]; + if (el) { + newHeights[index] = el.scrollHeight; + } + }); + setHeights(newHeights); + }, [logs]); + + const toggleStep = (step: string, index: number) => { if (expandedStep === step) { setExpandedStep(null); } else { setExpandedStep(step); + if (contentRefs.current[index]) { + contentRefs.current[index].style.height = `${heights[index]}px`; + } } }; return ( -
- {Object.entries(logs.details).map(([stepTitle, stepDetails]) => ( -
- - {expandedStep === stepTitle && ( -
- {stepDetails.map((detail, detailIndex) => ( +
(contentRefs.current[index] = el)} + className={clsx( + "overflow-hidden transition-[height] duration-500 ease-in-out", + expandedStep === stepTitle ? "h-auto" : "h-0" + )} + > + {stepDetails.map((detail, detailIndex) => ( +
{detail} - ))} -
- )} +
+ ))} +
))}
diff --git a/apps/browser/styles/globals.css b/apps/browser/styles/globals.css index 3cb9c360..ef8491a3 100644 --- a/apps/browser/styles/globals.css +++ b/apps/browser/styles/globals.css @@ -37,6 +37,10 @@ code { background-position: 56% 58%; } +.prose-condensed blockquote, dl, dd, h1, h2, h3, h4, h5, h6, hr, figure, p, pre { + @apply !m-0; +} + ::-webkit-scrollbar { @apply w-1 h-1 rounded; } From e6f3d6d757925d483f0f4570f9f9f6867d817c25 Mon Sep 17 00:00:00 2001 From: Colin Spence Date: Wed, 20 Dec 2023 23:42:45 -0700 Subject: [PATCH 05/12] Updates ChatDetails toggle logic --- .../browser/components/modals/ChatDetails.tsx | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 3015cea8..722f7d2d 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -1,16 +1,9 @@ -import { useEffect, useRef, useState } from "react"; +import { useRef, useState } from "react"; import Modal from "./ModalBase"; import ReactMarkdown from "react-markdown"; import { MessageSet } from "../ChatLogs"; import { CaretUp } from "@phosphor-icons/react"; import clsx from "clsx"; -import { Transition } from "@headlessui/react"; - -interface ChatDetailsProps { - isOpen: boolean; - onClose: () => void; - logs: MessageSet; -} export default function ChatDetails({ isOpen, @@ -18,29 +11,24 @@ export default function ChatDetails({ logs, }: ChatDetailsProps) { const [expandedStep, setExpandedStep] = useState(null); - const [heights, setHeights] = useState<{ [index: number]: number }>({}); const contentRefs = useRef<{ [index: number]: HTMLDivElement | null }>({}); - useEffect(() => { - const newHeights = []; - Object.keys(contentRefs.current).forEach((key) => { - const index = parseInt(key); - const el = contentRefs.current[index]; - if (el) { - newHeights[index] = el.scrollHeight; - } - }); - setHeights(newHeights); - }, [logs]); - const toggleStep = (step: string, index: number) => { + const currentEl = contentRefs.current[index]; + if (!currentEl) return; + + if (expandedStep !== null && expandedStep !== step) { + const currentIndex = Object.keys(logs.details).indexOf(expandedStep); + const currentExpandedEl = contentRefs.current[currentIndex]; + if (currentExpandedEl) currentExpandedEl.style.height = 0; + } + if (expandedStep === step) { + currentEl.style.height = 0; setExpandedStep(null); } else { + currentEl.style.height = `${currentEl.scrollHeight}px`; setExpandedStep(step); - if (contentRefs.current[index]) { - contentRefs.current[index].style.height = `${heights[index]}px`; - } } }; @@ -67,21 +55,22 @@ export default function ChatDetails({ weight="bold" size={14} className={clsx( - "transform text-white transition-transform duration-150 ease-in-out group-hover:text-cyan-400", + "transform text-white transition-transform duration-500 ease-in-out group-hover:text-cyan-500", expandedStep !== stepTitle && "rotate-180" )} />
(contentRefs.current[index] = el)} + ref={(el) => { + contentRefs.current[index] = el; + }} className={clsx( - "overflow-hidden transition-[height] duration-500 ease-in-out", - expandedStep === stepTitle ? "h-auto" : "h-0" + "step h-0 overflow-hidden transition-[height] duration-500 ease-in-out" )} > {stepDetails.map((detail, detailIndex) => ( -
- {detail} +
+ {detail}
))}
From 31ef5eb51fb5f826778560d3ccf3238b83b754e4 Mon Sep 17 00:00:00 2001 From: Cesar Date: Thu, 21 Dec 2023 10:21:00 +0100 Subject: [PATCH 06/12] chore: fix build --- apps/browser/components/ChatLogs.tsx | 1 - apps/browser/components/modals/ChatDetails.tsx | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 32339835..66915657 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -130,7 +130,6 @@ export default function ChatLogs({ }; }, [handleScroll]); - console.log(sanitizedLogs) return ( <>
diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 722f7d2d..b75807bb 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -5,6 +5,12 @@ import { MessageSet } from "../ChatLogs"; import { CaretUp } from "@phosphor-icons/react"; import clsx from "clsx"; +interface ChatDetailsProps { + isOpen: boolean + onClose: () => void + logs: MessageSet +} + export default function ChatDetails({ isOpen, onClose, @@ -20,11 +26,11 @@ export default function ChatDetails({ if (expandedStep !== null && expandedStep !== step) { const currentIndex = Object.keys(logs.details).indexOf(expandedStep); const currentExpandedEl = contentRefs.current[currentIndex]; - if (currentExpandedEl) currentExpandedEl.style.height = 0; + if (currentExpandedEl) currentExpandedEl.style.height = "0px"; } if (expandedStep === step) { - currentEl.style.height = 0; + currentEl.style.height = "0px"; setExpandedStep(null); } else { currentEl.style.height = `${currentEl.scrollHeight}px`; @@ -35,7 +41,7 @@ export default function ChatDetails({ return (
- {Object.entries(logs.details).map(([stepTitle, stepDetails], index) => ( + {logs.details && Object.entries(logs.details).map(([stepTitle, stepDetails], index) => (
Date: Thu, 21 Dec 2023 13:27:52 +0100 Subject: [PATCH 07/12] chore: improve logic of how the details are shown & persist status betwen convs --- apps/browser/app/page.tsx | 4 +- apps/browser/components/Chat.tsx | 1 + apps/browser/components/ChatLogs.tsx | 12 ++- .../browser/components/modals/ChatDetails.tsx | 94 ++++++++++--------- apps/browser/lib/hooks/useEvoService.ts | 15 ++- apps/browser/lib/services/evo/EvoThread.ts | 28 +++++- .../agents/src/functions/OnGoalAchieved.ts | 4 +- 7 files changed, 103 insertions(+), 55 deletions(-) diff --git a/apps/browser/app/page.tsx b/apps/browser/app/page.tsx index 1d028d5e..693ce741 100644 --- a/apps/browser/app/page.tsx +++ b/apps/browser/app/page.tsx @@ -36,7 +36,6 @@ function Dojo({ params }: { params: { id?: string } }) { const [, setAccountModalOpen] = useAtom(showAccountModalAtom); const [isAuthLoading, setIsAuthLoading] = useState(true); - const [currentStatus, setCurrentStatus] = useState(); const router = useRouter(); const { status: sessionStatus, data: sessionData } = useSession(); @@ -44,10 +43,9 @@ function Dojo({ params }: { params: { id?: string } }) { const { mutateAsync: createChat } = useCreateChat(); const { mutateAsync: updateChatTitle } = useUpdateChatTitle(); - const { logs, isConnected, isStarting, isRunning, handleStart } = useEvoService( + const { logs, isConnected, isStarting, isRunning, handleStart, currentStatus } = useEvoService( chatId, isAuthenticated, - setCurrentStatus ); const workspaceUploadUpdate = useWorkspaceUploadUpdate(); diff --git a/apps/browser/components/Chat.tsx b/apps/browser/components/Chat.tsx index f2b1cd4b..6c1754a9 100644 --- a/apps/browser/components/Chat.tsx +++ b/apps/browser/components/Chat.tsx @@ -23,6 +23,7 @@ export interface ChatLog { content?: string; user: string; color?: string; + created_at?: string } export interface ChatProps { diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 66915657..6eeb5b4e 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -9,7 +9,6 @@ import React, { import ReactMarkdown from "react-markdown"; import LoadingCircle from "./LoadingCircle"; import { ArrowSquareRight } from "@phosphor-icons/react"; -import Avatar from "./Avatar"; import Logo from "./Logo"; import { useSession } from "next-auth/react"; import ChatDetails from "./modals/ChatDetails"; @@ -27,12 +26,20 @@ export type MessageSet = { }; export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { + console.log(messages) if (!messages || !messages.length) return []; const dividedMessages: MessageSet[] = []; let currentMessageSet: MessageSet = { userMessage: "", details: {} }; let currentStepTitle = ""; let evoMessageFlag = false; + messages.sort((a, b) => { + return new Date(a.created_at as string).getTime() - new Date(b.created_at as string).getTime() + }) + // const s = [...messages].reduce((sanitizedLogs, currentMessage, index) => { + // return []; + // }, []); + messages.forEach((message, index) => { if (!message.title.startsWith("#")) { if ( @@ -61,7 +68,8 @@ export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { currentMessageSet.details[currentStepTitle] = []; } else if (currentStepTitle) { // Add detail to the current step - currentMessageSet.details[currentStepTitle].push(message.title); + const detailContent = message.content ? message.title.concat(`\n${message.content}`) : message.title + currentMessageSet.details[currentStepTitle].push(detailContent); } } diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index b75807bb..65ab4980 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -6,9 +6,9 @@ import { CaretUp } from "@phosphor-icons/react"; import clsx from "clsx"; interface ChatDetailsProps { - isOpen: boolean - onClose: () => void - logs: MessageSet + isOpen: boolean; + onClose: () => void; + logs: MessageSet; } export default function ChatDetails({ @@ -39,49 +39,59 @@ export default function ChatDetails({ }; return ( - + { + setExpandedStep(null); + onClose(); + }} + title="Details" + >
- {logs.details && Object.entries(logs.details).map(([stepTitle, stepDetails], index) => ( -
- -
{ - contentRefs.current[index] = el; - }} - className={clsx( - "step h-0 overflow-hidden transition-[height] duration-500 ease-in-out" - )} - > - {stepDetails.map((detail, detailIndex) => ( -
- {detail} + > + +
{ + contentRefs.current[index] = el; + }} + className={clsx( + "step h-0 overflow-hidden transition-[height] duration-500 ease-in-out" + )} + > + {stepDetails.map((detail, detailIndex) => ( +
+ {detail} +
+ ))}
- ))} -
-
- ))} +
+ ) + )}
); diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index 051848b4..4e38b747 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -25,12 +25,12 @@ import { useAtom } from "jotai"; export const useEvoService = ( chatId: string | "" | undefined, isAuthenticated: boolean, - handleStatusUpdate: (status: string) => void ): { logs: ChatLog[]; isConnected: boolean; isStarting: boolean; isRunning: boolean; + currentStatus: string | undefined; handleStart: (goal: string) => Promise; } => { const supabase = useSupabaseClient(); @@ -49,6 +49,7 @@ export const useEvoService = ( const [isStarting, setIsStarting] = useState(false); const [isRunning, setIsRunning] = useState(false); const [chatLog, setChatLogState] = useState([]); + const [currentStatus, setCurrentStatus] = useState(); // Mutations const { mutateAsync: addChatLog } = useAddChatLog(); @@ -76,6 +77,12 @@ export const useEvoService = ( } }; + const setStatus = (status?: string) => { + if (status) { + setCurrentStatus(status) + } + } + const disconnectEvoService = () => { setIsConnected(false); evoService.disconnect(); @@ -95,7 +102,7 @@ export const useEvoService = ( loadChatLog, loadWorkspace, onChatLogAdded: handleChatLogAdded, - onStatusUpdate: handleStatusUpdate, + onStatusUpdate: setCurrentStatus, onMessagesAdded: handleMessagesAdded, onVariableSet: handleVariableSet }; @@ -103,6 +110,7 @@ export const useEvoService = ( setIsRunning, setChatLog, setWorkspace, + setStatus, onGoalCapReached: () => { setCapReached(true); setAccountModalOpen(true); @@ -228,6 +236,7 @@ export const useEvoService = ( isConnected, isStarting, isRunning, - handleStart + handleStart, + currentStatus }; }; diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index e7c0fb10..591f9d2a 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -6,7 +6,7 @@ import { ChatLogType, ChatMessage, Workspace, - InMemoryWorkspace + InMemoryWorkspace, } from "@evo-ninja/agents"; export interface EvoThreadConfig { @@ -15,7 +15,10 @@ export interface EvoThreadConfig { loadWorkspace: (chatId: string) => Promise; onChatLogAdded: (chatLog: ChatLog) => Promise; onStatusUpdate: (status: string) => void; - onMessagesAdded: (type: ChatLogType, messages: ChatMessage[]) => Promise; + onMessagesAdded: ( + type: ChatLogType, + messages: ChatMessage[] + ) => Promise; onVariableSet: (key: string, value: string) => Promise; } @@ -25,11 +28,13 @@ export interface EvoThreadState { isLoading: boolean; logs: ChatLog[]; workspace: Workspace; + status?: string } export interface EvoThreadCallbacks { setIsRunning: (value: boolean) => void; setChatLog: (chatLog: ChatLog[]) => void; + setStatus: (status?: string) => void; setWorkspace: (workspace: Workspace) => void; onGoalCapReached: () => void; onError: (error: string) => void; @@ -96,6 +101,7 @@ export class EvoThread { this._callbacks.setIsRunning(this._state.isRunning); this._callbacks.setChatLog(this._state.logs); this._callbacks.setWorkspace(this._state.workspace); + this._callbacks.setStatus(this._state.status) } async start(options: EvoThreadStartOptions): Promise { @@ -141,7 +147,7 @@ export class EvoThread { this._config.onMessagesAdded, this._config.onVariableSet, (chatLog) => this.onChatLog(chatLog), - (status) => this._config.onStatusUpdate(status), + (status) => this.onStatusUpdate(status), () => this._callbacks?.onGoalCapReached(), // onError (error) => this._callbacks?.onError(error) @@ -202,6 +208,11 @@ export class EvoThread { await this._config.onChatLogAdded(chatLog); } + private onStatusUpdate(status: string): void { + this._callbacks?.setStatus(status) + this._state.status = status + } + private async runEvo(evo: Evo, goal: string): Promise { const iterator = evo.run({ goal }); @@ -222,6 +233,17 @@ export class EvoThread { this._callbacks?.setWorkspace(this._state.workspace); if (response.done) { + // If value is not present is because an unhandled error has happened in Evo + if ("value" in response.value) { + const isSuccess = response.value.value.type === "success"; + const message = { + title: `#### Information ${ + isSuccess ? "has been" : "could not be" + } retrieved`, + user: "evo", + }; + await this.onChatLog(message); + } this.setIsRunning(false); evo?.reset(); break; diff --git a/packages/agents/src/functions/OnGoalAchieved.ts b/packages/agents/src/functions/OnGoalAchieved.ts index b845e058..680e88bb 100644 --- a/packages/agents/src/functions/OnGoalAchieved.ts +++ b/packages/agents/src/functions/OnGoalAchieved.ts @@ -8,13 +8,13 @@ interface OnGoalAchievedFuncParameters { export class OnGoalAchievedFunction extends ScriptFunction { name: string = "agent_onGoalAchieved"; - description: string = "Informs the user that the goal has been achieved. Return a complete answer for user's question"; + description: string = "Informs the user that the goal has been achieved. Returns as message a complete and explicit answer for user's question"; parameters: any = { type: "object", properties: { message: { type: "string", - description: "Complete answer for user's question", + description: "Complete and explicit answer for user's question", }, }, required: ["message"], From 756bb893efc37703d519922e9e6197018e3bbe2c Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Thu, 21 Dec 2023 22:09:33 +0100 Subject: [PATCH 08/12] chore: updating BrowserLogger typings --- .../lib/services/evo/createEvoInstance.ts | 7 +++- apps/browser/lib/sys/logger/BrowserLogger.ts | 37 ++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index 1ed85f11..addaef85 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -42,7 +42,12 @@ export function createEvoInstance( title: message, }); }, - onStatusUpdate + onLogLevel: { + "notice": (msg: string) => { + onStatusUpdate(msg); + return Promise.resolve(); + } + } }); const logger = new Logger([browserLogger, new ConsoleLogger()], { promptUser: () => Promise.resolve("N/A"), diff --git a/apps/browser/lib/sys/logger/BrowserLogger.ts b/apps/browser/lib/sys/logger/BrowserLogger.ts index 3bc6d9dc..3fc2cd8e 100644 --- a/apps/browser/lib/sys/logger/BrowserLogger.ts +++ b/apps/browser/lib/sys/logger/BrowserLogger.ts @@ -1,30 +1,55 @@ import { ILogger } from "@evo-ninja/agent-utils"; +type LogLevel = "info" | "notice" | "success" | "warning" | "error"; + export interface BrowserLoggerConfig { onLog(markdown: string): Promise; - onStatusUpdate(status: string): void; + onLogLevel?: Partial Promise>>; } export class BrowserLogger implements ILogger { constructor(private _config: BrowserLoggerConfig) {} async info(info: string): Promise { - await this._config.onLog(info); + await Promise.all([ + this._config.onLog(info), + this.getOnLogLevel("info")(info) + ]); } async notice(msg: string): Promise { - await this._config.onStatusUpdate(msg); + await Promise.all([ + this._config.onLog(msg), + this.getOnLogLevel("notice")(msg) + ]); } async success(msg: string): Promise { - await this._config.onLog(msg); + await Promise.all([ + this._config.onLog(msg), + this.getOnLogLevel("success")(msg) + ]); } async warning(msg: string): Promise { - await this._config.onLog(msg); + await Promise.all([ + this._config.onLog(msg), + this.getOnLogLevel("warning")(msg), + ]); } async error(msg: string): Promise { - await this._config.onLog(msg); + await Promise.all([ + this._config.onLog(msg), + this.getOnLogLevel("error")(msg) + ]); + } + + private getOnLogLevel(level: LogLevel): (msg: string) => Promise { + return ( + this._config.onLogLevel && this._config.onLogLevel[level] + ) || ( + () => Promise.resolve() + ); } } From 872c8f896719649a58eb41a781e532b4a3f2ba58 Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Thu, 21 Dec 2023 22:20:21 +0100 Subject: [PATCH 09/12] chore: update EvoThread's handling of the setStatus callback --- apps/browser/app/page.tsx | 4 ++-- apps/browser/components/Chat.tsx | 6 +++--- apps/browser/components/ChatLogs.tsx | 6 +++--- apps/browser/lib/hooks/useEvoService.ts | 13 +++---------- apps/browser/lib/services/evo/EvoThread.ts | 16 +++++++++------- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/apps/browser/app/page.tsx b/apps/browser/app/page.tsx index 8fd923ee..a49659d1 100644 --- a/apps/browser/app/page.tsx +++ b/apps/browser/app/page.tsx @@ -47,7 +47,7 @@ function Dojo({ params }: { params: { id?: string } }) { const { mutateAsync: createChat } = useCreateChat(); const { mutateAsync: updateChatTitle } = useUpdateChatTitle(); - const { logs, isConnected, isStarting, isRunning, handleStart, currentStatus } = useEvoService( + const { logs, isConnected, isStarting, isRunning, handleStart, status } = useEvoService( chatId, isAuthenticated, ); @@ -187,7 +187,7 @@ function Dojo({ params }: { params: { id?: string } }) { workspaceUploadUpdate(workspace, uploads); } }} - currentStatus={currentStatus} + status={status} /> ) : (
diff --git a/apps/browser/components/Chat.tsx b/apps/browser/components/Chat.tsx index c7b3633d..747465dc 100644 --- a/apps/browser/components/Chat.tsx +++ b/apps/browser/components/Chat.tsx @@ -35,7 +35,7 @@ export interface ChatProps { isRunning: boolean; onGoalSubmit: (goal: string) => Promise; onUpload: (upload: InMemoryFile[]) => void; - currentStatus: string | undefined; + status: string | undefined; } const Chat: React.FC = ({ @@ -44,7 +44,7 @@ const Chat: React.FC = ({ isRunning, onGoalSubmit, onUpload, - currentStatus + status }: ChatProps) => { const [{ id: chatId, name: chatName }] = useAtom(chatInfoAtom); const [showDisclaimer, setShowDisclaimer] = useAtom(showDisclaimerAtom); @@ -90,7 +90,7 @@ const Chat: React.FC = ({ {shouldShowExamplePrompts ? ( ) : ( - + )}
(null); @@ -229,7 +229,7 @@ export default function ChatLogs({ }) } > - {currentStatus} + {status}
diff --git a/apps/browser/lib/hooks/useEvoService.ts b/apps/browser/lib/hooks/useEvoService.ts index c17b01cf..b002be88 100644 --- a/apps/browser/lib/hooks/useEvoService.ts +++ b/apps/browser/lib/hooks/useEvoService.ts @@ -30,7 +30,7 @@ export const useEvoService = ( isConnected: boolean; isStarting: boolean; isRunning: boolean; - currentStatus: string | undefined; + status: string | undefined; handleStart: (goal: string) => Promise; } => { const supabase = useSupabaseClient(); @@ -49,7 +49,7 @@ export const useEvoService = ( const [isStarting, setIsStarting] = useState(false); const [isRunning, setIsRunning] = useState(false); const [chatLog, setChatLogState] = useState([]); - const [currentStatus, setCurrentStatus] = useState(); + const [status, setStatus] = useState(undefined); // Mutations const { mutateAsync: addChatLog } = useAddChatLog(); @@ -77,12 +77,6 @@ export const useEvoService = ( } }; - const setStatus = (status?: string) => { - if (status) { - setCurrentStatus(status) - } - } - const disconnectEvoService = () => { setIsConnected(false); evoService.disconnect(); @@ -102,7 +96,6 @@ export const useEvoService = ( loadChatLog, loadWorkspace, onChatLogAdded: handleChatLogAdded, - onStatusUpdate: setCurrentStatus, onMessagesAdded: handleMessagesAdded, onVariableSet: handleVariableSet }; @@ -237,6 +230,6 @@ export const useEvoService = ( isStarting, isRunning, handleStart, - currentStatus + status }; }; diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 591f9d2a..6422fc02 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -14,7 +14,6 @@ export interface EvoThreadConfig { loadChatLog: (chatId: string) => Promise; loadWorkspace: (chatId: string) => Promise; onChatLogAdded: (chatLog: ChatLog) => Promise; - onStatusUpdate: (status: string) => void; onMessagesAdded: ( type: ChatLogType, messages: ChatMessage[] @@ -23,18 +22,18 @@ export interface EvoThreadConfig { } export interface EvoThreadState { - goal?: string; + goal: string | undefined; + status: string | undefined; isRunning: boolean; isLoading: boolean; logs: ChatLog[]; workspace: Workspace; - status?: string } export interface EvoThreadCallbacks { + setStatus: (status?: string) => void; setIsRunning: (value: boolean) => void; setChatLog: (chatLog: ChatLog[]) => void; - setStatus: (status?: string) => void; setWorkspace: (workspace: Workspace) => void; onGoalCapReached: () => void; onError: (error: string) => void; @@ -47,6 +46,8 @@ export interface EvoThreadStartOptions { } const INIT_STATE: EvoThreadState = { + goal: undefined, + status: undefined, isRunning: false, isLoading: false, logs: [], @@ -78,6 +79,7 @@ export class EvoThread { } // Dispatch init values + this._callbacks.setStatus(INIT_STATE.status); this._callbacks.setIsRunning(INIT_STATE.isRunning); this._callbacks.setChatLog(INIT_STATE.logs); this._callbacks.setWorkspace(INIT_STATE.workspace); @@ -98,10 +100,10 @@ export class EvoThread { } // Send current state to newly connected callbacks + this._callbacks.setStatus(this._state.status); this._callbacks.setIsRunning(this._state.isRunning); this._callbacks.setChatLog(this._state.logs); this._callbacks.setWorkspace(this._state.workspace); - this._callbacks.setStatus(this._state.status) } async start(options: EvoThreadStartOptions): Promise { @@ -209,8 +211,8 @@ export class EvoThread { } private onStatusUpdate(status: string): void { - this._callbacks?.setStatus(status) - this._state.status = status + this._state.status = status; + this._callbacks?.setStatus(status); } private async runEvo(evo: Evo, goal: string): Promise { From fa584b4bdd14eb608bc469ca2b002b798433640d Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Thu, 21 Dec 2023 22:36:54 +0100 Subject: [PATCH 10/12] fix: logging --- .../lib/services/evo/createEvoInstance.ts | 15 +++++++----- apps/browser/lib/sys/logger/BrowserLogger.ts | 24 ++++++++----------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/apps/browser/lib/services/evo/createEvoInstance.ts b/apps/browser/lib/services/evo/createEvoInstance.ts index addaef85..8c648a87 100644 --- a/apps/browser/lib/services/evo/createEvoInstance.ts +++ b/apps/browser/lib/services/evo/createEvoInstance.ts @@ -42,12 +42,15 @@ export function createEvoInstance( title: message, }); }, - onLogLevel: { - "notice": (msg: string) => { - onStatusUpdate(msg); - return Promise.resolve(); - } - } + onNotice: (msg: string) => { + onStatusUpdate(msg); + return Promise.resolve(); + }, + onSuccess: (msg: string) => + onChatLog({ + user: "evo", + title: msg, + }) }); const logger = new Logger([browserLogger, new ConsoleLogger()], { promptUser: () => Promise.resolve("N/A"), diff --git a/apps/browser/lib/sys/logger/BrowserLogger.ts b/apps/browser/lib/sys/logger/BrowserLogger.ts index 3fc2cd8e..d99c35c1 100644 --- a/apps/browser/lib/sys/logger/BrowserLogger.ts +++ b/apps/browser/lib/sys/logger/BrowserLogger.ts @@ -1,33 +1,29 @@ import { ILogger } from "@evo-ninja/agent-utils"; -type LogLevel = "info" | "notice" | "success" | "warning" | "error"; +type LogLevel = "info" | "warning" | "error"; export interface BrowserLoggerConfig { onLog(markdown: string): Promise; onLogLevel?: Partial Promise>>; + onNotice: (markdown: string) => Promise; + onSuccess: (markdown: string) => Promise; } export class BrowserLogger implements ILogger { constructor(private _config: BrowserLoggerConfig) {} - async info(info: string): Promise { - await Promise.all([ - this._config.onLog(info), - this.getOnLogLevel("info")(info) - ]); - } - async notice(msg: string): Promise { - await Promise.all([ - this._config.onLog(msg), - this.getOnLogLevel("notice")(msg) - ]); + await this._config.onNotice(msg); } async success(msg: string): Promise { + await this._config.onSuccess(msg); + } + + async info(info: string): Promise { await Promise.all([ - this._config.onLog(msg), - this.getOnLogLevel("success")(msg) + this._config.onLog(info), + this.getOnLogLevel("info")(info) ]); } From 232b2571c3b6828fcbc2bb4e9a349ad70ac899c6 Mon Sep 17 00:00:00 2001 From: Cesar Date: Thu, 21 Dec 2023 22:37:45 +0100 Subject: [PATCH 11/12] chore: move sanitize logs details & make algorithm prettier --- apps/browser/components/ChatLogs.tsx | 78 ++----------------- .../browser/components/modals/ChatDetails.tsx | 20 ++--- apps/browser/lib/services/evo/EvoThread.ts | 4 +- apps/browser/lib/utils/sanitizeLogsDetails.ts | 63 +++++++++++++++ 4 files changed, 83 insertions(+), 82 deletions(-) create mode 100644 apps/browser/lib/utils/sanitizeLogsDetails.ts diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 8869012c..4f4c48b3 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -12,82 +12,20 @@ import { ArrowSquareRight } from "@phosphor-icons/react"; import Logo from "./Logo"; import { useSession } from "next-auth/react"; import ChatDetails from "./modals/ChatDetails"; +import { sanitizeLogs } from "@/lib/utils/sanitizeLogsDetails"; export interface ChatLogsProps { logs: ChatLog[]; isRunning: boolean; currentStatus: string | undefined; - chatName: string -} - -export type MessageSet = { - userMessage: string; - evoMessage?: string; - details: Record; -}; - -export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { - console.log(messages) - if (!messages || !messages.length) return []; - const dividedMessages: MessageSet[] = []; - let currentMessageSet: MessageSet = { userMessage: "", details: {} }; - let currentStepTitle = ""; - let evoMessageFlag = false; - - messages.sort((a, b) => { - return new Date(a.created_at as string).getTime() - new Date(b.created_at as string).getTime() - }) - // const s = [...messages].reduce((sanitizedLogs, currentMessage, index) => { - // return []; - // }, []); - - messages.forEach((message, index) => { - if (!message.title.startsWith("#")) { - if ( - currentMessageSet.userMessage && - !evoMessageFlag && - message.user === "evo" - ) { - // This is the evoMessage after details - currentMessageSet.evoMessage = message.title; - evoMessageFlag = true; - } else if (!currentMessageSet.userMessage && message.user === "user") { - // This is the initial userMessage - currentMessageSet.userMessage = message.title; - evoMessageFlag = false; - } else { - // New set starts here - dividedMessages.push(currentMessageSet); - currentMessageSet = { userMessage: message.title, details: {} }; - currentStepTitle = ""; - evoMessageFlag = false; - } - } else { - if (message.title.startsWith("## ")) { - // New step title - currentStepTitle = message.title; - currentMessageSet.details[currentStepTitle] = []; - } else if (currentStepTitle) { - // Add detail to the current step - const detailContent = message.content ? message.title.concat(`\n${message.content}`) : message.title - currentMessageSet.details[currentStepTitle].push(detailContent); - } - } - - // Handle the last element - if (index === messages.length - 1) { - dividedMessages.push(currentMessageSet); - } - }); - - return dividedMessages; + chatName: string; } export default function ChatLogs({ logs, isRunning, currentStatus, - chatName + chatName, }: ChatLogsProps) { const listContainerRef = useRef(null); const [isAtBottom, setIsAtBottom] = useState(true); @@ -235,11 +173,11 @@ export default function ChatLogs({
)} - {!msg.evoMessage && !isRunning && ( - - There was an issue with your request, please try again - - )} + {!msg.evoMessage && !isRunning && ( + + There was an issue with your request, please try again + + )}
diff --git a/apps/browser/components/modals/ChatDetails.tsx b/apps/browser/components/modals/ChatDetails.tsx index 65ab4980..6b92d649 100644 --- a/apps/browser/components/modals/ChatDetails.tsx +++ b/apps/browser/components/modals/ChatDetails.tsx @@ -1,8 +1,8 @@ import { useRef, useState } from "react"; import Modal from "./ModalBase"; import ReactMarkdown from "react-markdown"; -import { MessageSet } from "../ChatLogs"; import { CaretUp } from "@phosphor-icons/react"; +import { MessageSet } from "@/lib/utils/sanitizeLogsDetails"; import clsx from "clsx"; interface ChatDetailsProps { @@ -66,14 +66,16 @@ export default function ChatDetails({ className="group flex w-full items-center justify-between p-4" > {stepTitle} - + {stepDetails.length > 0 && ( + + )}
{ diff --git a/apps/browser/lib/services/evo/EvoThread.ts b/apps/browser/lib/services/evo/EvoThread.ts index 591f9d2a..8c2a1a76 100644 --- a/apps/browser/lib/services/evo/EvoThread.ts +++ b/apps/browser/lib/services/evo/EvoThread.ts @@ -237,9 +237,7 @@ export class EvoThread { if ("value" in response.value) { const isSuccess = response.value.value.type === "success"; const message = { - title: `#### Information ${ - isSuccess ? "has been" : "could not be" - } retrieved`, + title: `## Goal has ${isSuccess ? "" : "not"} been achieved`, user: "evo", }; await this.onChatLog(message); diff --git a/apps/browser/lib/utils/sanitizeLogsDetails.ts b/apps/browser/lib/utils/sanitizeLogsDetails.ts new file mode 100644 index 00000000..cf84743d --- /dev/null +++ b/apps/browser/lib/utils/sanitizeLogsDetails.ts @@ -0,0 +1,63 @@ +import { ChatLog } from "@/components/Chat"; + +export type MessageSet = { + userMessage: string; + evoMessage?: string; + details: Record; +}; + +export function sanitizeLogs(messages: ChatLog[]): MessageSet[] { + if (!messages || !messages.length) return []; + + // First, sort the messages by their creation date + messages.sort( + (a, b) => + new Date(a.created_at as string).getTime() - + new Date(b.created_at as string).getTime() + ); + + return messages.reduce((sanitizedLogs, currentMessage) => { + const sanitizedLogsLength = sanitizedLogs.length; + const currentSet = + sanitizedLogsLength > 0 ? sanitizedLogs[sanitizedLogsLength - 1] : null; + + // If the message is from user, it means its the start of a new set of messages + if (currentMessage.user === "user") { + sanitizedLogs.push({ + userMessage: currentMessage.title, + details: {}, + evoMessage: undefined, + }); + return sanitizedLogs; + } + + // If there's no initialized set, don't try to fill details + if (!currentSet) { + return sanitizedLogs; + } + + // Only user message (goal) and evo's answer does not start with # + // Since user message is handled above, we now for sure that its evo's answer + if (!currentMessage.title.startsWith("#")) { + currentSet.evoMessage = currentMessage.title; + } else { + // Steps or onGoal{Status} are the one that starts with two # + if (currentMessage.title.startsWith("## ")) { + currentSet.details[currentMessage.title] = []; + } else { + // Get the title and/or content and attach to section + const detailKeys = Object.keys(currentSet.details); + const currentKey = detailKeys[detailKeys.length - 1]; + const detailContent = currentMessage.content + ? currentMessage.title.concat(`\n${currentMessage.content}`) + : currentMessage.title; + const currentStep = currentSet.details[currentKey]; + // To avoid errors in runtime, we guarantee that current step indeed exists + if (currentStep) { + currentStep.push(detailContent); + } + } + } + return sanitizedLogs; + }, []); +} From 5ce06f151fe3c3c9d7f3fa6d257f5711e6e86cfc Mon Sep 17 00:00:00 2001 From: dOrgJelli Date: Thu, 21 Dec 2023 22:49:59 +0100 Subject: [PATCH 12/12] chore: update failure case --- apps/browser/components/ChatLogs.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/browser/components/ChatLogs.tsx b/apps/browser/components/ChatLogs.tsx index 8bd92825..e3158b9e 100644 --- a/apps/browser/components/ChatLogs.tsx +++ b/apps/browser/components/ChatLogs.tsx @@ -128,7 +128,7 @@ export default function ChatLogs({ <>
Evo - {msg.evoMessage && !isRunning && ( + {!isRunning && (