From ceec3bc751dad689d8e1cce90abbbc748de8af5a Mon Sep 17 00:00:00 2001 From: Muhammad Ahmad Date: Tue, 29 Oct 2024 07:01:26 +0500 Subject: [PATCH 1/4] feat: add chat feedback with thumbs up/down buttons --- src/app/api/runs/feedback/route.ts | 88 ++++++++++++++++++++ src/components/Canvas.tsx | 4 +- src/components/ContentComposer.tsx | 24 +++--- src/components/Primitives.tsx | 128 ++++++++++++++++++++++++++--- src/hooks/use-graph/useGraph.tsx | 37 +++++---- src/hooks/useFeedback.ts | 101 +++++++++++++++++++++++ 6 files changed, 344 insertions(+), 38 deletions(-) create mode 100644 src/app/api/runs/feedback/route.ts create mode 100644 src/hooks/useFeedback.ts diff --git a/src/app/api/runs/feedback/route.ts b/src/app/api/runs/feedback/route.ts new file mode 100644 index 00000000..7f8076ad --- /dev/null +++ b/src/app/api/runs/feedback/route.ts @@ -0,0 +1,88 @@ +import { Client, Feedback } from "langsmith"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { runId, feedbackKey, score, comment } = body; + + if (!runId || !feedbackKey) { + return NextResponse.json( + { error: "`runId` and `feedbackKey` are required." }, + { status: 400 } + ); + } + + const lsClient = new Client({ + apiKey: process.env.LANGCHAIN_API_KEY, + }); + + const feedback = await lsClient.createFeedback(runId, feedbackKey, { + score, + comment, + }); + + return NextResponse.json( + { success: true, feedback: feedback }, + { status: 200 } + ); + } catch (error) { + console.error("Failed to process feedback request:", error); + + return NextResponse.json( + { error: "Failed to submit feedback." }, + { status: 500 } + ); + } +} + +export async function GET(req: NextRequest) { + try { + const searchParams = req.nextUrl.searchParams; + const runId = searchParams.get("runId"); + const feedbackKey = searchParams.get("feedbackKey"); + + if (!runId || !feedbackKey) { + return new NextResponse( + JSON.stringify({ + error: "`runId` and `feedbackKey` are required.", + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + } + ); + } + + const lsClient = new Client({ + apiKey: process.env.LANGCHAIN_API_KEY, + }); + + const runFeedback: Feedback[] = []; + + const run_feedback = await lsClient.listFeedback({ + runIds: [runId], + feedbackKeys: [feedbackKey], + }); + + for await (const feedback of run_feedback) { + runFeedback.push(feedback); + } + + return new NextResponse( + JSON.stringify({ + feedback: runFeedback, + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + } + ); + } catch (error) { + console.error("Failed to fetch feedback:", error); + return NextResponse.json( + { error: "Failed to fetch feedback." }, + { status: 500 } + ); + } +} diff --git a/src/components/Canvas.tsx b/src/components/Canvas.tsx index da86a947..9e735957 100644 --- a/src/components/Canvas.tsx +++ b/src/components/Canvas.tsx @@ -2,8 +2,8 @@ import { ArtifactRenderer } from "@/components/artifacts/ArtifactRenderer"; import { ContentComposerChatInterface } from "@/components/ContentComposer"; -import { useToast } from "@/hooks/use-toast"; import { useGraph } from "@/hooks/use-graph/useGraph"; +import { useToast } from "@/hooks/use-toast"; import { useStore } from "@/hooks/useStore"; import { useThread } from "@/hooks/useThread"; import { getLanguageTemplate } from "@/lib/get_language_template"; @@ -55,6 +55,7 @@ export function Canvas(props: CanvasProps) { isArtifactSaved, firstTokenReceived, selectedBlocks, + run, } = useGraph({ userId: props.user.id, threadId, @@ -190,6 +191,7 @@ export function Canvas(props: CanvasProps) { setChatStarted={setChatStarted} showNewThreadButton={chatStarted} handleQuickStart={handleQuickStart} + runId={run} /> {chatStarted && ( diff --git a/src/components/ContentComposer.tsx b/src/components/ContentComposer.tsx index 673de595..245438c5 100644 --- a/src/components/ContentComposer.tsx +++ b/src/components/ContentComposer.tsx @@ -1,24 +1,24 @@ "use client"; -import React, { useState } from "react"; +import { GraphInput } from "@/hooks/use-graph/useGraph"; +import { useToast } from "@/hooks/use-toast"; +import { + convertLangchainMessages, + convertToOpenAIFormat, +} from "@/lib/convert_messages"; +import { ProgrammingLanguageOptions, Reflections } from "@/types"; import { AppendMessage, AssistantRuntimeProvider, + useExternalMessageConverter, useExternalStoreRuntime, } from "@assistant-ui/react"; +import { BaseMessage, HumanMessage } from "@langchain/core/messages"; +import { Thread as ThreadType } from "@langchain/langgraph-sdk"; +import React, { useState } from "react"; import { v4 as uuidv4 } from "uuid"; import { Thread } from "./Primitives"; -import { useExternalMessageConverter } from "@assistant-ui/react"; -import { BaseMessage, HumanMessage } from "@langchain/core/messages"; -import { - convertLangchainMessages, - convertToOpenAIFormat, -} from "@/lib/convert_messages"; -import { GraphInput } from "@/hooks/use-graph/useGraph"; import { Toaster } from "./ui/toaster"; -import { ProgrammingLanguageOptions, Reflections } from "@/types"; -import { Thread as ThreadType } from "@langchain/langgraph-sdk"; -import { useToast } from "@/hooks/use-toast"; export interface ContentComposerChatInterfaceProps { messages: BaseMessage[]; @@ -41,6 +41,7 @@ export interface ContentComposerChatInterfaceProps { deleteThread: (id: string) => Promise; getUserThreads: (id: string) => Promise; userId: string; + runId: string; } export function ContentComposerChatInterface( @@ -106,6 +107,7 @@ export function ContentComposerChatInterface( userThreads={props.userThreads} switchSelectedThread={props.switchSelectedThread} deleteThread={props.deleteThread} + runId={props.runId} /> diff --git a/src/components/Primitives.tsx b/src/components/Primitives.tsx index 3812c5c2..60deefec 100644 --- a/src/components/Primitives.tsx +++ b/src/components/Primitives.tsx @@ -1,6 +1,7 @@ "use client"; import { + ActionBarPrimitive, ComposerPrimitive, MessagePrimitive, ThreadPrimitive, @@ -8,25 +9,28 @@ import { useMessageStore, useThreadRuntime, } from "@assistant-ui/react"; -import { type FC } from "react"; +import { useState, type FC } from "react"; +import { MarkdownText } from "@/components/ui/assistant-ui/markdown-text"; +import { TooltipIconButton } from "@/components/ui/assistant-ui/tooltip-icon-button"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; +import { useToast } from "@/hooks/use-toast"; +import { useFeedback } from "@/hooks/useFeedback"; +import { ProgrammingLanguageOptions, Reflections } from "@/types"; +import { Thread as ThreadType } from "@langchain/langgraph-sdk"; import { ArrowDownIcon, + NotebookPen, SendHorizontalIcon, SquarePen, - NotebookPen, + ThumbsDownIcon, + ThumbsUpIcon, } from "lucide-react"; -import { MarkdownText } from "@/components/ui/assistant-ui/markdown-text"; -import { TooltipIconButton } from "@/components/ui/assistant-ui/tooltip-icon-button"; -import { Thread as ThreadType } from "@langchain/langgraph-sdk"; import { useLangSmithLinkToolUI } from "./LangSmithLinkToolUI"; -import { ProgrammingLanguageOptions, Reflections } from "@/types"; +import { ProgrammingLanguagesDropdown } from "./ProgrammingLangDropdown"; import { ReflectionsDialog } from "./reflections-dialog/ReflectionsDialog"; import { ThreadHistory } from "./ThreadHistory"; -import { useToast } from "@/hooks/use-toast"; -import { ProgrammingLanguagesDropdown } from "./ProgrammingLangDropdown"; export interface ThreadProps { createThread: () => Promise; @@ -43,6 +47,7 @@ export interface ThreadProps { userThreads: ThreadType[]; switchSelectedThread: (thread: ThreadType) => void; deleteThread: (id: string) => Promise; + runId: string; } interface QuickStartButtonsProps { @@ -199,7 +204,9 @@ export const Thread: FC = (props: ThreadProps) => { components={{ UserMessage: UserMessage, EditComposer: EditComposer, - AssistantMessage: AssistantMessage, + AssistantMessage: (prop) => ( + + ), }} /> @@ -343,7 +350,11 @@ const EditComposer: FC = () => { ); }; -const AssistantMessage: FC = () => { +interface AssistantMessageProps { + runId: string; +} + +const AssistantMessage: FC = ({ runId }) => { return ( @@ -352,6 +363,9 @@ const AssistantMessage: FC = () => {
+ + +
); @@ -370,3 +384,97 @@ const CircleStopIcon = () => { ); }; + +interface AssistantMessageBarProps { + runId: string; +} + +const AssistantMessageBar = ({ runId }: AssistantMessageBarProps) => { + const [feedbackSubmitted, setFeedbackSubmitted] = useState(false); + + return ( + + {feedbackSubmitted ? ( +
+ Feedback received! Thank you! +
+ ) : ( + <> + + + + + + + + )} +
+ ); +}; + +interface FeedbackButtonProps { + runId: string; + setFeedbackSubmitted: React.Dispatch>; + feedbackValue: number; + icon: "thumbs-up" | "thumbs-down"; +} + +export const FeedbackButton: React.FC = ({ + runId, + setFeedbackSubmitted, + feedbackValue, + icon, +}) => { + const { sendFeedback } = useFeedback(); + const { toast } = useToast(); + + const handleClick = async () => { + try { + const res = await sendFeedback(runId, "feedback", feedbackValue); + if (res?.success) { + setFeedbackSubmitted(true); + } else { + toast({ + title: "Failed to submit feedback", + description: "Please try again later.", + variant: "destructive", + }); + } + } catch (_) { + toast({ + title: "Failed to submit feedback", + description: "Please try again later.", + variant: "destructive", + }); + } + }; + + return ( + + ); +}; diff --git a/src/hooks/use-graph/useGraph.tsx b/src/hooks/use-graph/useGraph.tsx index 3bda323e..b6ae695a 100644 --- a/src/hooks/use-graph/useGraph.tsx +++ b/src/hooks/use-graph/useGraph.tsx @@ -1,25 +1,31 @@ -import { useEffect, useRef, useState } from "react"; -import { AIMessage, BaseMessage } from "@langchain/core/messages"; -import { useToast } from "../use-toast"; -import { createClient } from "../utils"; +import { DEFAULT_INPUTS, THREAD_ID_COOKIE_NAME } from "@/constants"; +import { + isArtifactCodeContent, + isArtifactMarkdownContent, + isDeprecatedArtifactType, +} from "@/lib/artifact_content_types"; +import { setCookie } from "@/lib/cookies"; +import { reverseCleanContent } from "@/lib/normalize_string"; import { ArtifactLengthOptions, + ArtifactToolResponse, ArtifactType, ArtifactV3, + CodeHighlight, LanguageOptions, ProgrammingLanguageOptions, ReadingLevelOptions, - ArtifactToolResponse, RewriteArtifactMetaToolResponse, TextHighlight, - CodeHighlight, } from "@/types"; +import { AIMessage, BaseMessage } from "@langchain/core/messages"; import { parsePartialJson } from "@langchain/core/output_parsers"; -import { useRuns } from "../useRuns"; -import { reverseCleanContent } from "@/lib/normalize_string"; import { Thread } from "@langchain/langgraph-sdk"; -import { setCookie } from "@/lib/cookies"; -import { DEFAULT_INPUTS, THREAD_ID_COOKIE_NAME } from "@/constants"; +import { debounce } from "lodash"; +import { useEffect, useRef, useState } from "react"; +import { useToast } from "../use-toast"; +import { useRuns } from "../useRuns"; +import { createClient } from "../utils"; import { convertToArtifactV3, createNewGeneratedArtifactFromTool, @@ -28,12 +34,6 @@ import { updateHighlightedMarkdown, updateRewrittenArtifact, } from "./utils"; -import { - isArtifactCodeContent, - isArtifactMarkdownContent, - isDeprecatedArtifactType, -} from "@/lib/artifact_content_types"; -import { debounce } from "lodash"; // import { DEFAULT_ARTIFACTS, DEFAULT_MESSAGES } from "@/lib/dummy"; export interface GraphInput { @@ -99,6 +99,7 @@ export function useGraph(useGraphInput: UseGraphInput) { const [isArtifactSaved, setIsArtifactSaved] = useState(true); const [threadSwitched, setThreadSwitched] = useState(false); const [firstTokenReceived, setFirstTokenReceived] = useState(false); + const [run, setRun] = useState(""); // Very hacky way of ensuring updateState is not called when a thread is switched useEffect(() => { @@ -237,6 +238,7 @@ export function useGraph(useGraphInput: UseGraphInput) { let runId = ""; let followupMessageId = ""; // let lastMessage: AIMessage | undefined = undefined; + try { const stream = client.runs.stream( useGraphInput.threadId, @@ -283,6 +285,7 @@ export function useGraph(useGraphInput: UseGraphInput) { try { if (!runId && chunk.data?.metadata?.run_id) { runId = chunk.data.metadata.run_id; + setRun(runId); } if (chunk.data.event === "on_chain_start") { if ( @@ -714,6 +717,7 @@ export function useGraph(useGraphInput: UseGraphInput) { }); return newMessageWithToolCall; } + return msg; }); return newMsgs; @@ -861,5 +865,6 @@ export function useGraph(useGraphInput: UseGraphInput) { setUpdateRenderedArtifactRequired, isArtifactSaved, firstTokenReceived, + run, }; } diff --git a/src/hooks/useFeedback.ts b/src/hooks/useFeedback.ts new file mode 100644 index 00000000..98974b25 --- /dev/null +++ b/src/hooks/useFeedback.ts @@ -0,0 +1,101 @@ +import { Feedback } from "langsmith"; +import { useCallback, useState } from "react"; + +interface FeedbackResponse { + success: boolean; + feedback: Feedback; +} + +interface UseFeedbackResult { + isLoading: boolean; + error: string | null; + sendFeedback: ( + runId: string, + feedbackKey: string, + score: number, + comment?: string + ) => Promise; + getFeedback: ( + runId: string, + feedbackKey: string + ) => Promise; +} + +export function useFeedback(): UseFeedbackResult { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const sendFeedback = useCallback( + async ( + runId: string, + feedbackKey: string, + score: number, + comment?: string + ): Promise => { + setIsLoading(true); + setError(null); + + try { + const res = await fetch("/api/runs/feedback", { + method: "POST", + body: JSON.stringify({ runId, feedbackKey, score, comment }), + headers: { + "Content-Type": "application/json", + }, + }); + + if (!res.ok) { + return; + } + + return (await res.json()) as FeedbackResponse; + } catch (error) { + console.error("Error sending feedback:", error); + setError( + error instanceof Error ? error.message : "An unknown error occurred" + ); + return; + } finally { + setIsLoading(false); + } + }, + [] + ); + + const getFeedback = useCallback( + async ( + runId: string, + feedbackKey: string + ): Promise => { + setIsLoading(true); + setError(null); + try { + const res = await fetch( + `/api/runs/feedback?runId=${encodeURIComponent(runId)}&feedbackKey=${encodeURIComponent(feedbackKey)}` + ); + + if (!res.ok) { + return; + } + + return await res.json(); + } catch (error) { + console.error("Error getting feedback:", error); + setError( + error instanceof Error ? error.message : "An unknown error occurred" + ); + return; + } finally { + setIsLoading(false); + } + }, + [] + ); + + return { + isLoading, + sendFeedback, + getFeedback, + error, + }; +} From 24bd4a81c6bb8c0168982d3272819f368f5b6647 Mon Sep 17 00:00:00 2001 From: Muhammad Ahmad Date: Tue, 29 Oct 2024 10:33:28 +0500 Subject: [PATCH 2/4] feat: Add chat feedback only on last AI message --- src/components/Primitives.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/Primitives.tsx b/src/components/Primitives.tsx index 60deefec..acfd6efc 100644 --- a/src/components/Primitives.tsx +++ b/src/components/Primitives.tsx @@ -6,6 +6,7 @@ import { MessagePrimitive, ThreadPrimitive, useComposerStore, + useMessage, useMessageStore, useThreadRuntime, } from "@assistant-ui/react"; @@ -355,6 +356,7 @@ interface AssistantMessageProps { } const AssistantMessage: FC = ({ runId }) => { + const isLast = useMessage().isLast; return ( @@ -363,9 +365,11 @@ const AssistantMessage: FC = ({ runId }) => {
- - - + {isLast && ( + + + + )}
); From fed56dd378a32bed111cefeacbc0f8a287259548 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 29 Oct 2024 16:09:07 -0700 Subject: [PATCH 3/4] format/lint --- src/components/chat-interface/feedback.tsx | 2 +- src/components/chat-interface/messages.tsx | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/chat-interface/feedback.tsx b/src/components/chat-interface/feedback.tsx index 1ac665c4..13d68c88 100644 --- a/src/components/chat-interface/feedback.tsx +++ b/src/components/chat-interface/feedback.tsx @@ -54,4 +54,4 @@ export const FeedbackButton: React.FC = ({ )} ); -}; \ No newline at end of file +}; diff --git a/src/components/chat-interface/messages.tsx b/src/components/chat-interface/messages.tsx index 92bb9a84..5e6d59b5 100644 --- a/src/components/chat-interface/messages.tsx +++ b/src/components/chat-interface/messages.tsx @@ -1,6 +1,10 @@ "use client"; -import { ActionBarPrimitive, MessagePrimitive, useMessage } from "@assistant-ui/react"; +import { + ActionBarPrimitive, + MessagePrimitive, + useMessage, +} from "@assistant-ui/react"; import { useState, type FC } from "react"; import { MarkdownText } from "@/components/ui/assistant-ui/markdown-text"; From e8796d8b915fa0a34a1d6ad09f3ab11ab4ca9df7 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Tue, 29 Oct 2024 16:36:06 -0700 Subject: [PATCH 4/4] cr --- src/components/chat-interface/feedback.tsx | 36 +++++++++++++----- src/components/chat-interface/messages.tsx | 43 +++++++++++++++++----- src/components/chat-interface/thread.tsx | 9 ++++- src/constants.ts | 2 +- src/contexts/GraphContext.tsx | 6 +++ src/hooks/useFeedback.ts | 2 +- 6 files changed, 74 insertions(+), 24 deletions(-) diff --git a/src/components/chat-interface/feedback.tsx b/src/components/chat-interface/feedback.tsx index 13d68c88..0b06ad67 100644 --- a/src/components/chat-interface/feedback.tsx +++ b/src/components/chat-interface/feedback.tsx @@ -1,22 +1,32 @@ import { useToast } from "@/hooks/use-toast"; -import { useFeedback } from "@/hooks/useFeedback"; -import { Button } from "../ui/button"; +import { FeedbackResponse } from "@/hooks/useFeedback"; import { ThumbsUpIcon, ThumbsDownIcon } from "lucide-react"; +import { Dispatch, FC, SetStateAction } from "react"; +import { cn } from "@/lib/utils"; +import { TooltipIconButton } from "../ui/assistant-ui/tooltip-icon-button"; interface FeedbackButtonProps { runId: string; - setFeedbackSubmitted: React.Dispatch>; + setFeedbackSubmitted: Dispatch>; + sendFeedback: ( + runId: string, + feedbackKey: string, + score: number, + comment?: string + ) => Promise; feedbackValue: number; icon: "thumbs-up" | "thumbs-down"; + isLoading: boolean; } -export const FeedbackButton: React.FC = ({ +export const FeedbackButton: FC = ({ runId, setFeedbackSubmitted, + sendFeedback, + isLoading, feedbackValue, icon, }) => { - const { sendFeedback } = useFeedback(); const { toast } = useToast(); const handleClick = async () => { @@ -40,18 +50,24 @@ export const FeedbackButton: React.FC = ({ } }; + const tooltip = `Give ${icon === "thumbs-up" ? "positive" : "negative"} feedback on this run`; + return ( - + ); }; diff --git a/src/components/chat-interface/messages.tsx b/src/components/chat-interface/messages.tsx index 5e6d59b5..342d0c18 100644 --- a/src/components/chat-interface/messages.tsx +++ b/src/components/chat-interface/messages.tsx @@ -5,17 +5,25 @@ import { MessagePrimitive, useMessage, } from "@assistant-ui/react"; -import { useState, type FC } from "react"; +import React, { Dispatch, SetStateAction, type FC } from "react"; import { MarkdownText } from "@/components/ui/assistant-ui/markdown-text"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { FeedbackButton } from "./feedback"; +import { TighterText } from "../ui/header"; +import { useFeedback } from "@/hooks/useFeedback"; interface AssistantMessageProps { runId: string | undefined; + feedbackSubmitted: boolean; + setFeedbackSubmitted: Dispatch>; } -export const AssistantMessage: FC = ({ runId }) => { +export const AssistantMessage: FC = ({ + runId, + feedbackSubmitted, + setFeedbackSubmitted, +}) => { const isLast = useMessage().isLast; return ( @@ -27,7 +35,11 @@ export const AssistantMessage: FC = ({ runId }) => { {isLast && runId && ( - + )} @@ -47,11 +59,16 @@ export const UserMessage: FC = () => { interface AssistantMessageBarProps { runId: string; + feedbackSubmitted: boolean; + setFeedbackSubmitted: Dispatch>; } -const AssistantMessageBar = ({ runId }: AssistantMessageBarProps) => { - const [feedbackSubmitted, setFeedbackSubmitted] = useState(false); - +const AssistantMessageBarComponent = ({ + runId, + feedbackSubmitted, + setFeedbackSubmitted, +}: AssistantMessageBarProps) => { + const { isLoading, sendFeedback } = useFeedback(); return ( { className="flex items-center mt-2" > {feedbackSubmitted ? ( -
+ Feedback received! Thank you! -
+ ) : ( <> @@ -85,3 +106,5 @@ const AssistantMessageBar = ({ runId }: AssistantMessageBarProps) => {
); }; + +const AssistantMessageBar = React.memo(AssistantMessageBarComponent); diff --git a/src/components/chat-interface/thread.tsx b/src/components/chat-interface/thread.tsx index 23fd1cc0..9376032c 100644 --- a/src/components/chat-interface/thread.tsx +++ b/src/components/chat-interface/thread.tsx @@ -50,7 +50,7 @@ export const Thread: FC = (props: ThreadProps) => { const { userData: { user }, threadData: { createThread, modelName, setModelName, assistantId }, - graphData: { clearState, runId }, + graphData: { clearState, runId, feedbackSubmitted, setFeedbackSubmitted }, } = useGraphContext(); useLangSmithLinkToolUI(); @@ -117,7 +117,12 @@ export const Thread: FC = (props: ThreadProps) => { components={{ UserMessage: UserMessage, AssistantMessage: (prop) => ( - + ), }} /> diff --git a/src/constants.ts b/src/constants.ts index 27513f5a..6fbf260c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,5 @@ export const LANGGRAPH_API_URL = - process.env.LANGGRAPH_API_URL ?? "http://localhost:56339"; + process.env.LANGGRAPH_API_URL ?? "http://localhost:50944"; // v2 is tied to the 'open-canvas-prod' deployment. export const ASSISTANT_ID_COOKIE = "oc_assistant_id_v2"; // export const ASSISTANT_ID_COOKIE = "oc_assistant_id"; diff --git a/src/contexts/GraphContext.tsx b/src/contexts/GraphContext.tsx index 84636067..a522e800 100644 --- a/src/contexts/GraphContext.tsx +++ b/src/contexts/GraphContext.tsx @@ -62,6 +62,8 @@ interface GraphData { updateRenderedArtifactRequired: boolean; isArtifactSaved: boolean; firstTokenReceived: boolean; + feedbackSubmitted: boolean; + setFeedbackSubmitted: Dispatch>; setArtifact: Dispatch>; setSelectedBlocks: Dispatch>; setSelectedArtifact: (index: number) => void; @@ -128,6 +130,7 @@ export function GraphProvider({ children }: { children: ReactNode }) { const [threadSwitched, setThreadSwitched] = useState(false); const [firstTokenReceived, setFirstTokenReceived] = useState(false); const [runId, setRunId] = useState(); + const [feedbackSubmitted, setFeedbackSubmitted] = useState(false); useEffect(() => { if (userData.user) return; @@ -292,6 +295,7 @@ export function GraphProvider({ children }: { children: ReactNode }) { setIsStreaming(true); setRunId(undefined); + setFeedbackSubmitted(false); // The root level run ID of this stream let runId = ""; let followupMessageId = ""; @@ -934,6 +938,8 @@ export function GraphProvider({ children }: { children: ReactNode }) { updateRenderedArtifactRequired, isArtifactSaved, firstTokenReceived, + feedbackSubmitted, + setFeedbackSubmitted, setArtifact, setSelectedBlocks, setSelectedArtifact, diff --git a/src/hooks/useFeedback.ts b/src/hooks/useFeedback.ts index 98974b25..b70fe99a 100644 --- a/src/hooks/useFeedback.ts +++ b/src/hooks/useFeedback.ts @@ -1,7 +1,7 @@ import { Feedback } from "langsmith"; import { useCallback, useState } from "react"; -interface FeedbackResponse { +export interface FeedbackResponse { success: boolean; feedback: Feedback; }