mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-06 13:22:12 +00:00
Show executed code in web app chat message references
This commit is contained in:
@@ -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]);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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: "",
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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 || "",
|
||||||
|
|||||||
Reference in New Issue
Block a user