Show executed code in web app chat message references

This commit is contained in:
Debanjum Singh Solanky
2024-10-09 17:31:50 -07:00
parent a98f97ed5e
commit b373073f47
7 changed files with 159 additions and 13 deletions

View File

@@ -12,7 +12,12 @@ import { processMessageChunk } from "../common/chatFunctions";
import "katex/dist/katex.min.css"; import "katex/dist/katex.min.css";
import { Context, OnlineContext, StreamMessage } from "../components/chatMessage/chatMessage"; import {
CodeContext,
Context,
OnlineContext,
StreamMessage,
} from "../components/chatMessage/chatMessage";
import { useIPLocationData, useIsMobileWidth, welcomeConsole } from "../common/utils"; import { useIPLocationData, useIsMobileWidth, welcomeConsole } from "../common/utils";
import ChatInputArea, { ChatOptions } from "../components/chatInputArea/chatInputArea"; import ChatInputArea, { ChatOptions } from "../components/chatInputArea/chatInputArea";
import { useAuthenticatedData } from "../common/auth"; import { useAuthenticatedData } from "../common/auth";
@@ -167,6 +172,7 @@ export default function Chat() {
trainOfThought: [], trainOfThought: [],
context: [], context: [],
onlineContext: {}, onlineContext: {},
codeContext: {},
completed: false, completed: false,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
rawQuery: queryToProcess || "", rawQuery: queryToProcess || "",
@@ -195,6 +201,7 @@ export default function Chat() {
// Track context used for chat response // Track context used for chat response
let context: Context[] = []; let context: Context[] = [];
let onlineContext: OnlineContext = {}; let onlineContext: OnlineContext = {};
let codeContext: CodeContext = {};
while (true) { while (true) {
const { done, value } = await reader.read(); const { done, value } = await reader.read();
@@ -221,11 +228,12 @@ export default function Chat() {
} }
// Track context used for chat response. References are rendered at the end of the chat // Track context used for chat response. References are rendered at the end of the chat
({ context, onlineContext } = processMessageChunk( ({ context, onlineContext, codeContext } = processMessageChunk(
event, event,
currentMessage, currentMessage,
context, context,
onlineContext, onlineContext,
codeContext,
)); ));
setMessages([...messages]); setMessages([...messages]);

View File

@@ -1,13 +1,20 @@
import { Context, OnlineContext, StreamMessage } from "../components/chatMessage/chatMessage"; import {
CodeContext,
Context,
OnlineContext,
StreamMessage,
} from "../components/chatMessage/chatMessage";
export interface RawReferenceData { export interface RawReferenceData {
context?: Context[]; context?: Context[];
onlineContext?: OnlineContext; onlineContext?: OnlineContext;
codeContext?: CodeContext;
} }
export interface ResponseWithReferences { export interface ResponseWithReferences {
context?: Context[]; context?: Context[];
online?: OnlineContext; online?: OnlineContext;
codeContext?: CodeContext;
response?: string; response?: string;
} }
@@ -63,10 +70,11 @@ export function processMessageChunk(
currentMessage: StreamMessage, currentMessage: StreamMessage,
context: Context[] = [], context: Context[] = [],
onlineContext: OnlineContext = {}, onlineContext: OnlineContext = {},
): { context: Context[]; onlineContext: OnlineContext } { codeContext: CodeContext = {},
): { context: Context[]; onlineContext: OnlineContext; codeContext: CodeContext } {
const chunk = convertMessageChunkToJson(rawChunk); const chunk = convertMessageChunkToJson(rawChunk);
if (!currentMessage || !chunk || !chunk.type) return { context, onlineContext }; if (!currentMessage || !chunk || !chunk.type) return { context, onlineContext, codeContext };
if (chunk.type === "status") { if (chunk.type === "status") {
console.log(`status: ${chunk.data}`); console.log(`status: ${chunk.data}`);
@@ -77,7 +85,8 @@ export function processMessageChunk(
if (references.context) context = references.context; if (references.context) context = references.context;
if (references.onlineContext) onlineContext = references.onlineContext; if (references.onlineContext) onlineContext = references.onlineContext;
return { context, onlineContext }; if (references.codeContext) codeContext = references.codeContext;
return { context, onlineContext, codeContext };
} else if (chunk.type === "message") { } else if (chunk.type === "message") {
const chunkData = chunk.data; const chunkData = chunk.data;
if (chunkData !== null && typeof chunkData === "object") { if (chunkData !== null && typeof chunkData === "object") {
@@ -102,13 +111,14 @@ export function processMessageChunk(
console.log(`Completed streaming: ${new Date()}`); console.log(`Completed streaming: ${new Date()}`);
// Append any references after all the data has been streamed // Append any references after all the data has been streamed
if (codeContext) currentMessage.codeContext = codeContext;
if (onlineContext) currentMessage.onlineContext = onlineContext; if (onlineContext) currentMessage.onlineContext = onlineContext;
if (context) currentMessage.context = context; if (context) currentMessage.context = context;
// Mark current message streaming as completed // Mark current message streaming as completed
currentMessage.completed = true; currentMessage.completed = true;
} }
return { context, onlineContext }; return { context, onlineContext, codeContext };
} }
export function handleImageResponse(imageJson: any, liveStream: boolean): ResponseWithReferences { export function handleImageResponse(imageJson: any, liveStream: boolean): ResponseWithReferences {

View File

@@ -295,6 +295,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
message: message.rawQuery, message: message.rawQuery,
context: [], context: [],
onlineContext: {}, onlineContext: {},
codeContext: {},
created: message.timestamp, created: message.timestamp,
by: "you", by: "you",
automationId: "", automationId: "",
@@ -318,6 +319,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
message: message.rawResponse, message: message.rawResponse,
context: message.context, context: message.context,
onlineContext: message.onlineContext, onlineContext: message.onlineContext,
codeContext: message.codeContext,
created: message.timestamp, created: message.timestamp,
by: "khoj", by: "khoj",
automationId: "", automationId: "",
@@ -338,6 +340,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
message: props.pendingMessage, message: props.pendingMessage,
context: [], context: [],
onlineContext: {}, onlineContext: {},
codeContext: {},
created: new Date().getTime().toString(), created: new Date().getTime().toString(),
by: "you", by: "you",
automationId: "", automationId: "",

View File

@@ -97,6 +97,26 @@ export interface OnlineContextData {
peopleAlsoAsk: PeopleAlsoAsk[]; peopleAlsoAsk: PeopleAlsoAsk[];
} }
export interface CodeContext {
[key: string]: CodeContextData;
}
export interface CodeContextData {
code: string;
results: {
success: boolean;
output_files: CodeContextFile[];
std_out: string;
std_err: string;
code_runtime: number;
};
}
export interface CodeContextFile {
filename: string;
b64_data: string;
}
interface Intent { interface Intent {
type: string; type: string;
query: string; query: string;
@@ -111,6 +131,7 @@ export interface SingleChatMessage {
created: string; created: string;
context: Context[]; context: Context[];
onlineContext: OnlineContext; onlineContext: OnlineContext;
codeContext: CodeContext;
rawQuery?: string; rawQuery?: string;
intent?: Intent; intent?: Intent;
agent?: AgentData; agent?: AgentData;
@@ -122,6 +143,7 @@ export interface StreamMessage {
trainOfThought: string[]; trainOfThought: string[];
context: Context[]; context: Context[];
onlineContext: OnlineContext; onlineContext: OnlineContext;
codeContext: CodeContext;
completed: boolean; completed: boolean;
rawQuery: string; rawQuery: string;
timestamp: string; timestamp: string;
@@ -539,6 +561,7 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
const allReferences = constructAllReferences( const allReferences = constructAllReferences(
props.chatMessage.context, props.chatMessage.context,
props.chatMessage.onlineContext, props.chatMessage.onlineContext,
props.chatMessage.codeContext,
); );
return ( return (
@@ -560,6 +583,7 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
isMobileWidth={props.isMobileWidth} isMobileWidth={props.isMobileWidth}
notesReferenceCardData={allReferences.notesReferenceCardData} notesReferenceCardData={allReferences.notesReferenceCardData}
onlineReferenceCardData={allReferences.onlineReferenceCardData} onlineReferenceCardData={allReferences.onlineReferenceCardData}
codeReferenceCardData={allReferences.codeReferenceCardData}
/> />
</div> </div>
<div className={styles.chatFooter}> <div className={styles.chatFooter}>

View File

@@ -11,7 +11,7 @@ const md = new markdownIt({
typographer: true, typographer: true,
}); });
import { Context, WebPage, OnlineContext } from "../chatMessage/chatMessage"; import { Context, WebPage, OnlineContext, CodeContext } from "../chatMessage/chatMessage";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { import {
@@ -94,11 +94,67 @@ function NotesContextReferenceCard(props: NotesContextReferenceCardProps) {
); );
} }
interface CodeContextReferenceCardProps {
code: string;
output: string;
error: string;
showFullContent: boolean;
}
function CodeContextReferenceCard(props: CodeContextReferenceCardProps) {
const fileIcon = getIconFromFilename(".py", "w-6 h-6 text-muted-foreground inline-flex mr-2");
const snippet = DOMPurify.sanitize(props.code);
const [isHovering, setIsHovering] = useState(false);
return (
<>
<Popover open={isHovering && !props.showFullContent} onOpenChange={setIsHovering}>
<PopoverTrigger asChild>
<Card
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
className={`${props.showFullContent ? "w-auto" : "w-[200px]"} overflow-hidden break-words text-balance rounded-lg p-2 bg-muted border-none`}
>
<h3
className={`${props.showFullContent ? "block" : "line-clamp-1"} text-muted-foreground}`}
>
{fileIcon}
Code
</h3>
<p
className={`${props.showFullContent ? "block" : "overflow-hidden line-clamp-2"}`}
>
{snippet}
</p>
</Card>
</PopoverTrigger>
<PopoverContent className="w-[400px] mx-2">
<Card
className={`w-auto overflow-hidden break-words text-balance rounded-lg p-2 border-none`}
>
<h3 className={`line-clamp-2 text-muted-foreground}`}>
{fileIcon}
Code
</h3>
<p className={`overflow-hidden line-clamp-3`}>{snippet}</p>
</Card>
</PopoverContent>
</Popover>
</>
);
}
export interface ReferencePanelData { export interface ReferencePanelData {
notesReferenceCardData: NotesContextReferenceData[]; notesReferenceCardData: NotesContextReferenceData[];
onlineReferenceCardData: OnlineReferenceData[]; onlineReferenceCardData: OnlineReferenceData[];
} }
export interface CodeReferenceData {
code: string;
output: string;
error: string;
}
interface OnlineReferenceData { interface OnlineReferenceData {
title: string; title: string;
description: string; description: string;
@@ -214,9 +270,27 @@ function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) {
); );
} }
export function constructAllReferences(contextData: Context[], onlineData: OnlineContext) { export function constructAllReferences(
contextData: Context[],
onlineData: OnlineContext,
codeContext: CodeContext,
) {
const onlineReferences: OnlineReferenceData[] = []; const onlineReferences: OnlineReferenceData[] = [];
const contextReferences: NotesContextReferenceData[] = []; const contextReferences: NotesContextReferenceData[] = [];
const codeReferences: CodeReferenceData[] = [];
if (codeContext) {
for (const [key, value] of Object.entries(codeContext)) {
if (!value.results) {
continue;
}
codeReferences.push({
code: value.code,
output: value.results.std_out,
error: value.results.std_err,
});
}
}
if (onlineData) { if (onlineData) {
let localOnlineReferences = []; let localOnlineReferences = [];
@@ -298,12 +372,14 @@ export function constructAllReferences(contextData: Context[], onlineData: Onlin
return { return {
notesReferenceCardData: contextReferences, notesReferenceCardData: contextReferences,
onlineReferenceCardData: onlineReferences, onlineReferenceCardData: onlineReferences,
codeReferenceCardData: codeReferences,
}; };
} }
export interface TeaserReferenceSectionProps { export interface TeaserReferenceSectionProps {
notesReferenceCardData: NotesContextReferenceData[]; notesReferenceCardData: NotesContextReferenceData[];
onlineReferenceCardData: OnlineReferenceData[]; onlineReferenceCardData: OnlineReferenceData[];
codeReferenceCardData: CodeReferenceData[];
isMobileWidth: boolean; isMobileWidth: boolean;
} }
@@ -315,16 +391,27 @@ export function TeaserReferencesSection(props: TeaserReferenceSectionProps) {
}, [props.isMobileWidth]); }, [props.isMobileWidth]);
const notesDataToShow = props.notesReferenceCardData.slice(0, numTeaserSlots); const notesDataToShow = props.notesReferenceCardData.slice(0, numTeaserSlots);
const codeDataToShow = props.codeReferenceCardData.slice(
0,
numTeaserSlots - notesDataToShow.length,
);
const onlineDataToShow = const onlineDataToShow =
notesDataToShow.length < numTeaserSlots notesDataToShow.length + codeDataToShow.length < numTeaserSlots
? props.onlineReferenceCardData.slice(0, numTeaserSlots - notesDataToShow.length) ? props.onlineReferenceCardData.slice(
0,
numTeaserSlots - codeDataToShow.length - notesDataToShow.length,
)
: []; : [];
const shouldShowShowMoreButton = const shouldShowShowMoreButton =
props.notesReferenceCardData.length > 0 || props.onlineReferenceCardData.length > 0; props.notesReferenceCardData.length > 0 ||
props.codeReferenceCardData.length > 0 ||
props.onlineReferenceCardData.length > 0;
const numReferences = const numReferences =
props.notesReferenceCardData.length + props.onlineReferenceCardData.length; props.notesReferenceCardData.length +
props.codeReferenceCardData.length +
props.onlineReferenceCardData.length;
if (numReferences === 0) { if (numReferences === 0) {
return null; return null;
@@ -346,6 +433,15 @@ export function TeaserReferencesSection(props: TeaserReferenceSectionProps) {
/> />
); );
})} })}
{codeDataToShow.map((code, index) => {
return (
<CodeContextReferenceCard
showFullContent={false}
{...code}
key={`code-${index}`}
/>
);
})}
{onlineDataToShow.map((online, index) => { {onlineDataToShow.map((online, index) => {
return ( return (
<GenericOnlineReferenceCard <GenericOnlineReferenceCard

View File

@@ -5,6 +5,7 @@ import { useAuthenticatedData } from "@/app/common/auth";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import ChatMessage, { import ChatMessage, {
CodeContext,
Context, Context,
OnlineContext, OnlineContext,
OnlineContextData, OnlineContextData,
@@ -46,6 +47,7 @@ interface SupplementReferences {
interface ResponseWithReferences { interface ResponseWithReferences {
context?: Context[]; context?: Context[];
online?: OnlineContext; online?: OnlineContext;
code?: CodeContext;
response?: string; response?: string;
} }
@@ -192,6 +194,7 @@ function ReferenceVerification(props: ReferenceVerificationProps) {
context: [], context: [],
created: new Date().toISOString(), created: new Date().toISOString(),
onlineContext: {}, onlineContext: {},
codeContext: {},
}} }}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
/> />
@@ -622,6 +625,7 @@ export default function FactChecker() {
context: [], context: [],
created: new Date().toISOString(), created: new Date().toISOString(),
onlineContext: {}, onlineContext: {},
codeContext: {},
}} }}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
/> />

View File

@@ -164,6 +164,7 @@ export default function SharedChat() {
trainOfThought: [], trainOfThought: [],
context: [], context: [],
onlineContext: {}, onlineContext: {},
codeContext: {},
completed: false, completed: false,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
rawQuery: queryToProcess || "", rawQuery: queryToProcess || "",