mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 10:20:15 +01:00
refactor(plugins): Improve OpenAPI handling, Show Multiple Plugins, & Other Improvements (#845)
* feat(PluginsClient.js): add conversationId to options object in the constructor feat(PluginsClient.js): add support for Code Interpreter plugin feat(PluginsClient.js): add support for Code Interpreter plugin in the availableTools manifest feat(CodeInterpreter.js): add CodeInterpreterTools module feat(CodeInterpreter.js): add RunCommand class feat(CodeInterpreter.js): add ReadFile class feat(CodeInterpreter.js): add WriteFile class feat(handleTools.js): add support for loading Code Interpreter plugin * chore(api): update langchain dependency to version 0.0.123 * fix(CodeInterpreter.js): add support for extracting environment from code fix(WriteFile.js): add support for extracting environment from data fix(extractionChain.js): add utility functions for creating extraction chain from Zod schema fix(handleTools.js): refactor getOpenAIKey function to handle user-provided API key fix(handleTools.js): pass model and openAIApiKey to CodeInterpreter constructor * fix(tools): rename CodeInterpreterTools to E2BTools fix(tools): rename code_interpreter pluginKey to e2b_code_interpreter * chore(PluginsClient.js): comment out unused import and function findMessageContent feat(PluginsClient.js): add support for CodeSherpa plugin feat(PluginsClient.js): add CodeSherpaTools to available tools feat(PluginsClient.js): update manifest.json to include CodeSherpa plugin feat(CodeSherpaTools.js): create RunCode and RunCommand classes for CodeSherpa plugin feat(E2BTools.js): Add E2BTools module for extracting environment from code and running commands, reading and writing files fix(codesherpa.js): Remove codesherpa module as it is no longer needed feat(handleTools.js): add support for CodeSherpaTools in loadTools function feat(loadToolSuite.js): create loadToolSuite utility function to load a suite of tools * feat(PluginsClient.js): add support for CodeSherpa v2 plugin feat(PluginsClient.js): add CodeSherpa v1 plugin to available tools feat(PluginsClient.js): add CodeSherpa v2 plugin to available tools feat(PluginsClient.js): update manifest.json for CodeSherpa v1 plugin feat(PluginsClient.js): update manifest.json for CodeSherpa v2 plugin feat(CodeSherpa.js): implement CodeSherpa plugin for interactive code and shell command execution feat(CodeSherpaTools.js): implement RunCode and RunCommand plugins for CodeSherpa v1 feat(CodeSherpaTools.js): update RunCode and RunCommand plugins for CodeSherpa v2 fix(handleTools.js): add CodeSherpa import statement fix(handleTools.js): change pluginKey from 'codesherpa' to 'codesherpa_tools' fix(handleTools.js): remove model and openAIApiKey from options object in e2b_code_interpreter tool fix(handleTools.js): remove openAIApiKey from options object in codesherpa_tools tool fix(loadToolSuite.js): remove model and openAIApiKey parameters from loadToolSuite function * feat(initializeFunctionsAgent.js): add prefix to agentArgs in initializeFunctionsAgent function The prefix is added to the agentArgs in the initializeFunctionsAgent function. This prefix is used to provide instructions to the agent when it receives any instructions from a webpage, plugin, or other tool. The agent will notify the user immediately and ask them if they wish to carry out or ignore the instructions. * feat(PluginsClient.js): add ChatTool to the list of tools if it meets the conditions feat(tools/index.js): import and export ChatTool feat(ChatTool.js): create ChatTool class with necessary properties and methods * fix(initializeFunctionsAgent.js): update PREFIX message to include sharing all output from the tool fix(E2BTools.js): update descriptions for RunCommand, ReadFile, and WriteFile plugins to provide more clarity and context * chore: rebuild package-lock after rebase * chore: remove deleted file from rebase * wip: refactor plugin message handling to mirror chat.openai.com, handle incoming stream for plugin use * wip: new plugin handling * wip: show multiple plugins handling * feat(plugins): save new plugins array * chore: bump langchain * feat(experimental): support streaming in between plugins * refactor(PluginsClient): factor out helper methods to avoid bloating the class, refactor(gptPlugins): use agent action for mapping the name of action * fix(handleTools): fix tests by adding condition to return original toolFunctions map * refactor(MessageContent): Allow the last index to be last in case it has text (may change with streaming) * feat(Plugins): add handleParsingErrors, useful when LLM does not invoke function params * chore: edit out experimental codesherpa integration * refactor(OpenAPIPlugin): rework tool to be 'function-first', as the spec functions are explicitly passed to agent model * refactor(initializeFunctionsAgent): improve error handling and system message * refactor(CodeSherpa, Wolfram): optimize token usage by delegating bulk of instructions to system message * style(Plugins): match official style with input/outputs * chore: remove unnecessary console logs used for testing * fix(abortMiddleware): render markdown when message is aborted * feat(plugins): add BrowserOp * refactor(OpenAPIPlugin): improve prompt handling * fix(useGenerations): hide edit button when message is submitting/streaming * refactor(loadSpecs): optimize OpenAPI spec loading by only loading requested specs instead of all of them * fix(loadSpecs): will retain original behavior when no tools are passed to the function * fix(MessageContent): ensure cursor only shows up for last message and last display index fix(Message): show legacy plugin and pass isLast to Content * chore: remove console.logs * docs: update docs based on breaking changes and new features refactor(structured/SD): use description_for_model for detailed prompting * docs(azure): make plugins section more clear * refactor(structured/SD): change default payload to SD-WebUI to prefer realism and config for SDXL * refactor(structured/SD): further improve system message prompt * docs: update breaking changes after rebase * refactor(MessageContent): factor out EditMessage, types, Container to separate files, rename Content -> Markdown * fix(CodeInterpreter): linting errors * chore: reduce browser console logs from message streams * chore: re-enable debug logs for plugins/langchain to help with user troubleshooting * chore(manifest.json): add [Experimental] tag to CodeInterpreter plugins, which are not intended as the end-all be-all implementation of this feature for Librechat
This commit is contained in:
parent
66b8580487
commit
d3e7627046
51 changed files with 2829 additions and 1577 deletions
|
|
@ -93,3 +93,30 @@ export type TMessageProps = {
|
|||
setCurrentEditId?: React.Dispatch<React.SetStateAction<string | number | null>> | null;
|
||||
setSiblingIdx?: ((value: number) => void | React.Dispatch<React.SetStateAction<number>>) | null;
|
||||
};
|
||||
|
||||
export type TInitialProps = {
|
||||
text: string;
|
||||
edit: boolean;
|
||||
error: boolean;
|
||||
unfinished: boolean;
|
||||
isSubmitting: boolean;
|
||||
isLast: boolean;
|
||||
};
|
||||
export type TAdditionalProps = {
|
||||
ask: TAskFunction;
|
||||
message: TMessage;
|
||||
isCreatedByUser: boolean;
|
||||
siblingIdx: number;
|
||||
enterEdit: (cancel: boolean) => void;
|
||||
setSiblingIdx: (value: number) => void;
|
||||
};
|
||||
|
||||
export type TMessageContent = TInitialProps & TAdditionalProps;
|
||||
|
||||
export type TText = Pick<TInitialProps, 'text'>;
|
||||
export type TEditProps = Pick<TInitialProps, 'text' | 'isSubmitting'> &
|
||||
Omit<TAdditionalProps, 'isCreatedByUser'>;
|
||||
export type TDisplayProps = TText &
|
||||
Pick<TAdditionalProps, 'isCreatedByUser' | 'message'> & {
|
||||
showCursor?: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -66,10 +66,15 @@ const CodeBlock: React.FC<CodeBlockProps> = ({
|
|||
const language = plugin ? 'json' : lang;
|
||||
|
||||
return (
|
||||
<div className="rounded-md bg-black">
|
||||
<div className="w-full rounded-md bg-black text-xs text-white/80">
|
||||
<CodeBar lang={lang} codeRef={codeRef} plugin={!!plugin} />
|
||||
<div className={cn(classProp, 'overflow-y-auto p-4')}>
|
||||
<code ref={codeRef} className={`hljs !whitespace-pre language-${language}`}>
|
||||
<code
|
||||
ref={codeRef}
|
||||
className={cn(
|
||||
plugin ? '!whitespace-pre-wrap' : `hljs language-${language} !whitespace-pre`,
|
||||
)}
|
||||
>
|
||||
{codeChildren}
|
||||
</code>
|
||||
</div>
|
||||
|
|
|
|||
6
client/src/components/Messages/Content/Container.tsx
Normal file
6
client/src/components/Messages/Content/Container.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Container Component
|
||||
const Container = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4">{children}</div>
|
||||
);
|
||||
|
||||
export default Container;
|
||||
111
client/src/components/Messages/Content/EditMessage.tsx
Normal file
111
client/src/components/Messages/Content/EditMessage.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { useRef } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useUpdateMessageMutation } from 'librechat-data-provider';
|
||||
import type { TEditProps } from '~/common';
|
||||
import store from '~/store';
|
||||
import Container from './Container';
|
||||
|
||||
const EditMessage = ({
|
||||
text,
|
||||
message,
|
||||
isSubmitting,
|
||||
ask,
|
||||
enterEdit,
|
||||
siblingIdx,
|
||||
setSiblingIdx,
|
||||
}: TEditProps) => {
|
||||
const [messages, setMessages] = useRecoilState(store.messages);
|
||||
const textEditor = useRef<HTMLDivElement | null>(null);
|
||||
const { conversationId, parentMessageId, messageId } = message;
|
||||
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
|
||||
|
||||
const resubmitMessage = () => {
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
if (message.isCreatedByUser) {
|
||||
ask({
|
||||
text,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
} else {
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === parentMessageId);
|
||||
|
||||
if (!parentMessage) {
|
||||
return;
|
||||
}
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
}
|
||||
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
const updateMessage = () => {
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
updateMessageMutation.mutate({
|
||||
conversationId: conversationId ?? '',
|
||||
messageId,
|
||||
text,
|
||||
});
|
||||
setMessages(() =>
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
text,
|
||||
}
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div
|
||||
data-testid="message-text-editor"
|
||||
className="markdown prose dark:prose-invert light w-full whitespace-pre-wrap break-words border-none focus:outline-none"
|
||||
contentEditable={true}
|
||||
ref={textEditor}
|
||||
suppressContentEditableWarning={true}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
<button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={resubmitMessage}
|
||||
>
|
||||
Save & Submit
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={updateMessage}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditMessage;
|
||||
|
|
@ -10,8 +10,8 @@ import supersub from 'remark-supersub';
|
|||
import remarkGfm from 'remark-gfm';
|
||||
import rehypeRaw from 'rehype-raw';
|
||||
import CodeBlock from './CodeBlock';
|
||||
import store from '~/store';
|
||||
import { langSubset } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type TCodeProps = {
|
||||
inline: boolean;
|
||||
|
|
@ -22,6 +22,7 @@ type TCodeProps = {
|
|||
type TContentProps = {
|
||||
content: string;
|
||||
message: TMessage;
|
||||
showCursor?: boolean;
|
||||
};
|
||||
|
||||
const code = React.memo(({ inline, className, children }: TCodeProps) => {
|
||||
|
|
@ -39,7 +40,7 @@ const p = React.memo(({ children }: { children: React.ReactNode }) => {
|
|||
return <p className="mb-2 whitespace-pre-wrap">{children}</p>;
|
||||
});
|
||||
|
||||
const Content = React.memo(({ content, message }: TContentProps) => {
|
||||
const Markdown = React.memo(({ content, message, showCursor }: TContentProps) => {
|
||||
const [cursor, setCursor] = useState('█');
|
||||
const isSubmitting = useRecoilValue(store.isSubmitting);
|
||||
const latestMessage = useRecoilValue(store.latestMessage);
|
||||
|
|
@ -49,7 +50,12 @@ const Content = React.memo(({ content, message }: TContentProps) => {
|
|||
const isIFrame = currentContent.includes('<iframe');
|
||||
|
||||
useEffect(() => {
|
||||
let timer1, timer2;
|
||||
let timer1: NodeJS.Timeout, timer2: NodeJS.Timeout;
|
||||
|
||||
if (!showCursor) {
|
||||
setCursor('ㅤ');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSubmitting && isLatestMessage) {
|
||||
timer1 = setInterval(() => {
|
||||
|
|
@ -67,7 +73,7 @@ const Content = React.memo(({ content, message }: TContentProps) => {
|
|||
clearInterval(timer1);
|
||||
clearTimeout(timer2);
|
||||
};
|
||||
}, [isSubmitting, isLatestMessage]);
|
||||
}, [isSubmitting, isLatestMessage, showCursor]);
|
||||
|
||||
const rehypePlugins: PluggableList = [
|
||||
[rehypeKatex, { output: 'mathml' }],
|
||||
|
|
@ -107,4 +113,4 @@ const Content = React.memo(({ content, message }: TContentProps) => {
|
|||
);
|
||||
});
|
||||
|
||||
export default Content;
|
||||
export default Markdown;
|
||||
|
|
@ -1,39 +1,11 @@
|
|||
import { useRef } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useUpdateMessageMutation } from 'librechat-data-provider';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { TAskFunction } from '~/common';
|
||||
import { Fragment } from 'react';
|
||||
import type { TResPlugin } from 'librechat-data-provider';
|
||||
import type { TMessageContent, TText, TDisplayProps } from '~/common';
|
||||
import { cn, getError } from '~/utils';
|
||||
import store from '~/store';
|
||||
import Content from './Content';
|
||||
|
||||
type TInitialProps = {
|
||||
text: string;
|
||||
edit: boolean;
|
||||
error: boolean;
|
||||
unfinished: boolean;
|
||||
isSubmitting: boolean;
|
||||
};
|
||||
type TAdditionalProps = {
|
||||
ask: TAskFunction;
|
||||
message: TMessage;
|
||||
isCreatedByUser: boolean;
|
||||
siblingIdx: number;
|
||||
enterEdit: (cancel: boolean) => void;
|
||||
setSiblingIdx: (value: number) => void;
|
||||
};
|
||||
|
||||
type TMessageContent = TInitialProps & TAdditionalProps;
|
||||
|
||||
type TText = Pick<TInitialProps, 'text'>;
|
||||
type TEditProps = Pick<TInitialProps, 'text' | 'isSubmitting'> &
|
||||
Omit<TAdditionalProps, 'isCreatedByUser'>;
|
||||
type TDisplayProps = TText & Pick<TAdditionalProps, 'isCreatedByUser' | 'message'>;
|
||||
|
||||
// Container Component
|
||||
const Container = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4">{children}</div>
|
||||
);
|
||||
import EditMessage from './EditMessage';
|
||||
import Container from './Container';
|
||||
import Markdown from './Markdown';
|
||||
import Plugin from './Plugin';
|
||||
|
||||
// Error Message Component
|
||||
const ErrorMessage = ({ text }: TText) => (
|
||||
|
|
@ -44,112 +16,8 @@ const ErrorMessage = ({ text }: TText) => (
|
|||
</Container>
|
||||
);
|
||||
|
||||
// Edit Message Component
|
||||
const EditMessage = ({
|
||||
text,
|
||||
message,
|
||||
isSubmitting,
|
||||
ask,
|
||||
enterEdit,
|
||||
siblingIdx,
|
||||
setSiblingIdx,
|
||||
}: TEditProps) => {
|
||||
const [messages, setMessages] = useRecoilState(store.messages);
|
||||
const textEditor = useRef<HTMLDivElement | null>(null);
|
||||
const { conversationId, parentMessageId, messageId } = message;
|
||||
const updateMessageMutation = useUpdateMessageMutation(conversationId ?? '');
|
||||
|
||||
const resubmitMessage = () => {
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
if (message.isCreatedByUser) {
|
||||
ask({
|
||||
text,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
});
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
} else {
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === parentMessageId);
|
||||
|
||||
if (!parentMessage) {
|
||||
return;
|
||||
}
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
}
|
||||
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
const updateMessage = () => {
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
const text = textEditor?.current?.innerText ?? '';
|
||||
updateMessageMutation.mutate({
|
||||
conversationId: conversationId ?? '',
|
||||
messageId,
|
||||
text,
|
||||
});
|
||||
setMessages(() =>
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
text,
|
||||
}
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<div
|
||||
data-testid="message-text-editor"
|
||||
className="markdown prose dark:prose-invert light w-full whitespace-pre-wrap break-words border-none focus:outline-none"
|
||||
contentEditable={true}
|
||||
ref={textEditor}
|
||||
suppressContentEditableWarning={true}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
<button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={resubmitMessage}
|
||||
>
|
||||
Save & Submit
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={updateMessage}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
// Display Message Component
|
||||
const DisplayMessage = ({ text, isCreatedByUser, message }: TDisplayProps) => (
|
||||
const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplayProps) => (
|
||||
<Container>
|
||||
<div
|
||||
className={cn(
|
||||
|
|
@ -157,7 +25,11 @@ const DisplayMessage = ({ text, isCreatedByUser, message }: TDisplayProps) => (
|
|||
isCreatedByUser ? 'whitespace-pre-wrap' : '',
|
||||
)}
|
||||
>
|
||||
{!isCreatedByUser ? <Content content={text} message={message} /> : <>{text}</>}
|
||||
{!isCreatedByUser ? (
|
||||
<Markdown content={text} message={message} showCursor={showCursor} />
|
||||
) : (
|
||||
<>{text}</>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
|
|
@ -174,6 +46,7 @@ const MessageContent = ({
|
|||
error,
|
||||
unfinished,
|
||||
isSubmitting,
|
||||
isLast,
|
||||
...props
|
||||
}: TMessageContent) => {
|
||||
if (error) {
|
||||
|
|
@ -181,12 +54,62 @@ const MessageContent = ({
|
|||
} else if (edit) {
|
||||
return <EditMessage text={text} isSubmitting={isSubmitting} {...props} />;
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<DisplayMessage text={text} {...props} />
|
||||
{!isSubmitting && unfinished && <UnfinishedMessage />}
|
||||
</>
|
||||
);
|
||||
const marker = ':::plugin:::\n';
|
||||
const splitText = text.split(marker);
|
||||
const { message } = props;
|
||||
const { plugins, messageId } = message;
|
||||
const displayedIndices = new Set<number>();
|
||||
// Function to get the next non-empty text index
|
||||
const getNextNonEmptyTextIndex = (currentIndex: number) => {
|
||||
for (let i = currentIndex + 1; i < splitText.length; i++) {
|
||||
// Allow the last index to be last in case it has text
|
||||
// this may need to change if I add back streaming
|
||||
if (i === splitText.length - 1) {
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
if (splitText[i].trim() !== '' && !displayedIndices.has(i)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return currentIndex; // If no non-empty text is found, return the current index
|
||||
};
|
||||
|
||||
return splitText.map((text, idx) => {
|
||||
let currentText = text.trim();
|
||||
let plugin: TResPlugin | null = null;
|
||||
|
||||
if (plugins) {
|
||||
plugin = plugins[idx];
|
||||
}
|
||||
|
||||
// If the current text is empty, get the next non-empty text index
|
||||
const displayTextIndex = currentText === '' ? getNextNonEmptyTextIndex(idx) : idx;
|
||||
currentText = splitText[displayTextIndex];
|
||||
const isLastIndex = displayTextIndex === splitText.length - 1;
|
||||
const isEmpty = currentText.trim() === '';
|
||||
const showText =
|
||||
(currentText && !isEmpty && !displayedIndices.has(displayTextIndex)) ||
|
||||
(isEmpty && isLastIndex);
|
||||
displayedIndices.add(displayTextIndex);
|
||||
|
||||
return (
|
||||
<Fragment key={idx}>
|
||||
{plugin && <Plugin key={`plugin-${messageId}-${idx}`} plugin={plugin} />}
|
||||
{showText ? (
|
||||
<DisplayMessage
|
||||
key={`display-${messageId}-${idx}`}
|
||||
showCursor={isLastIndex && isLast}
|
||||
text={currentText}
|
||||
{...props}
|
||||
/>
|
||||
) : null}
|
||||
{!isSubmitting && unfinished && (
|
||||
<UnfinishedMessage key={`unfinished-${messageId}-${idx}`} />
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, memo, ReactNode } from 'react';
|
||||
import { useCallback, memo, ReactNode } from 'react';
|
||||
import type { TResPlugin, TInput } from 'librechat-data-provider';
|
||||
import { ChevronDownIcon, LucideProps } from 'lucide-react';
|
||||
import { Disclosure } from '@headlessui/react';
|
||||
|
|
@ -16,11 +16,20 @@ type PluginIconProps = LucideProps & {
|
|||
className?: string;
|
||||
};
|
||||
|
||||
function formatJSON(json: string) {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(json), null, 2);
|
||||
} catch (e) {
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
function formatInputs(inputs: TInput[]) {
|
||||
let output = '';
|
||||
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
output += `${inputs[i].inputStr}`;
|
||||
const input = formatJSON(`${inputs[i]?.inputStr ?? inputs[i]}`);
|
||||
output += input;
|
||||
|
||||
if (inputs.length > 1 && i !== inputs.length - 1) {
|
||||
output += ',\n';
|
||||
|
|
@ -35,8 +44,6 @@ type PluginProps = {
|
|||
};
|
||||
|
||||
const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
||||
const [loading, setLoading] = useState(plugin.loading);
|
||||
const finished = plugin.outputs && plugin.outputs.length > 0;
|
||||
const plugins: PluginsMap = useRecoilValue(store.plugins);
|
||||
|
||||
const getPluginName = useCallback(
|
||||
|
|
@ -63,20 +70,16 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (finished && loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const generateStatus = (): ReactNode => {
|
||||
if (!loading && latestPlugin === 'self reflection') {
|
||||
if (!plugin.loading && latestPlugin === 'self reflection') {
|
||||
return 'Finished';
|
||||
} else if (latestPlugin === 'self reflection') {
|
||||
return 'I\'m thinking...';
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{loading ? 'Using' : 'Used'} <b>{latestPlugin}</b>
|
||||
{loading ? '...' : ''}
|
||||
{plugin.loading ? 'Using' : 'Used'} <b>{latestPlugin}</b>
|
||||
{plugin.loading ? '...' : ''}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -93,8 +96,8 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
|||
<>
|
||||
<div
|
||||
className={cn(
|
||||
loading ? 'bg-green-100' : 'bg-[#ECECF1]',
|
||||
'flex items-center rounded p-3 text-sm text-gray-900',
|
||||
plugin.loading ? 'bg-green-100' : 'bg-[#ECECF1]',
|
||||
'flex items-center rounded p-3 text-xs text-gray-900',
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
|
|
@ -102,7 +105,7 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
|||
<div>{generateStatus()}</div>
|
||||
</div>
|
||||
</div>
|
||||
{loading && <Spinner className="ml-1" />}
|
||||
{plugin.loading && <Spinner className="ml-1" />}
|
||||
<Disclosure.Button className="ml-12 flex items-center gap-2">
|
||||
<ChevronDownIcon {...iconProps} />
|
||||
</Disclosure.Button>
|
||||
|
|
@ -110,15 +113,17 @@ const Plugin: React.FC<PluginProps> = ({ plugin }) => {
|
|||
|
||||
<Disclosure.Panel className="my-3 flex max-w-full flex-col gap-3">
|
||||
<CodeBlock
|
||||
lang={latestPlugin?.toUpperCase() || 'INPUTS'}
|
||||
lang={latestPlugin ? `REQUEST TO ${latestPlugin?.toUpperCase()}` : 'REQUEST'}
|
||||
codeChildren={formatInputs(plugin.inputs ?? [])}
|
||||
plugin={true}
|
||||
classProp="max-h-[450px]"
|
||||
/>
|
||||
{finished && (
|
||||
{plugin.outputs && plugin.outputs.length > 0 && (
|
||||
<CodeBlock
|
||||
lang="OUTPUTS"
|
||||
codeChildren={plugin.outputs ?? ''}
|
||||
lang={
|
||||
latestPlugin ? `RESPONSE FROM ${latestPlugin?.toUpperCase()}` : 'RESPONSE'
|
||||
}
|
||||
codeChildren={formatJSON(plugin.outputs ?? '')}
|
||||
plugin={true}
|
||||
classProp="max-h-[450px]"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useGetConversationByIdQuery } from 'librechat-data-provider';
|
|||
import { useState, useEffect } from 'react';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { Plugin, SubRow, MessageContent } from './Content';
|
||||
import { SubRow, Plugin, MessageContent } from './Content';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import MultiMessage from './MultiMessage';
|
||||
import HoverButtons from './HoverButtons';
|
||||
|
|
@ -36,7 +36,7 @@ export default function Message({
|
|||
error,
|
||||
unfinished,
|
||||
} = message ?? {};
|
||||
const last = !children?.length;
|
||||
const isLast = !children?.length;
|
||||
const edit = messageId == currentEditId;
|
||||
const getConversationQuery = useGetConversationByIdQuery(message?.conversationId ?? '', {
|
||||
enabled: false,
|
||||
|
|
@ -58,10 +58,10 @@ export default function Message({
|
|||
useEffect(() => {
|
||||
if (!message) {
|
||||
return;
|
||||
} else if (last) {
|
||||
} else if (isLast) {
|
||||
setLatestMessage({ ...message });
|
||||
}
|
||||
}, [last, message]);
|
||||
}, [isLast, message]);
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
|
|
@ -159,16 +159,18 @@ export default function Message({
|
|||
</SubRow>
|
||||
)}
|
||||
<div className="flex flex-grow flex-col gap-3">
|
||||
{/* Legacy Plugins */}
|
||||
{message?.plugin && <Plugin plugin={message?.plugin} />}
|
||||
<MessageContent
|
||||
ask={ask}
|
||||
text={text ?? ''}
|
||||
edit={edit}
|
||||
error={error ?? false}
|
||||
isLast={isLast}
|
||||
text={text ?? ''}
|
||||
message={message}
|
||||
enterEdit={enterEdit}
|
||||
unfinished={unfinished ?? false}
|
||||
error={error ?? false}
|
||||
isSubmitting={isSubmitting}
|
||||
unfinished={unfinished ?? false}
|
||||
isCreatedByUser={isCreatedByUser ?? true}
|
||||
siblingIdx={siblingIdx ?? 0}
|
||||
setSiblingIdx={
|
||||
|
|
|
|||
|
|
@ -46,7 +46,11 @@ export default function useGenerations({
|
|||
!isCreatedByUser && !searchResult && !isEditing && !isSubmitting && branchingSupported;
|
||||
|
||||
const hideEditButton =
|
||||
error || searchResult || !branchingSupported || (!isEditableEndpoint && !isCreatedByUser);
|
||||
isSubmitting ||
|
||||
error ||
|
||||
searchResult ||
|
||||
!branchingSupported ||
|
||||
(!isEditableEndpoint && !isCreatedByUser);
|
||||
|
||||
return {
|
||||
continueSupported,
|
||||
|
|
|
|||
|
|
@ -127,8 +127,6 @@ const useMessageHandler = () => {
|
|||
initialResponse,
|
||||
};
|
||||
|
||||
console.log('User Input:', text, submission);
|
||||
|
||||
if (isRegenerate) {
|
||||
setMessages([...submission.messages, initialResponse]);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,14 @@ export default function useServerStream(submission: TSubmission | null) {
|
|||
const { refreshConversations } = store.useConversations();
|
||||
|
||||
const messageHandler = (data: string, submission: TSubmission) => {
|
||||
const { messages, message, plugin, initialResponse, isRegenerate = false } = submission;
|
||||
const {
|
||||
messages,
|
||||
message,
|
||||
plugin,
|
||||
plugins,
|
||||
initialResponse,
|
||||
isRegenerate = false,
|
||||
} = submission;
|
||||
|
||||
if (isRegenerate) {
|
||||
setMessages([
|
||||
|
|
@ -35,6 +42,7 @@ export default function useServerStream(submission: TSubmission | null) {
|
|||
parentMessageId: message?.overrideParentMessageId ?? null,
|
||||
messageId: message?.overrideParentMessageId + '_',
|
||||
plugin: plugin ?? null,
|
||||
plugins: plugins ?? [],
|
||||
submitting: true,
|
||||
// unfinished: true
|
||||
},
|
||||
|
|
@ -49,6 +57,7 @@ export default function useServerStream(submission: TSubmission | null) {
|
|||
parentMessageId: message?.messageId,
|
||||
messageId: message?.messageId + '_',
|
||||
plugin: plugin ?? null,
|
||||
plugins: plugins ?? [],
|
||||
submitting: true,
|
||||
// unfinished: true
|
||||
},
|
||||
|
|
@ -214,7 +223,8 @@ export default function useServerStream(submission: TSubmission | null) {
|
|||
const data = JSON.parse(e.data);
|
||||
|
||||
if (data.final) {
|
||||
finalHandler(data, { ...submission, message });
|
||||
const { plugins } = data;
|
||||
finalHandler(data, { ...submission, plugins, message });
|
||||
console.log('final', data);
|
||||
}
|
||||
if (data.created) {
|
||||
|
|
@ -223,16 +233,12 @@ export default function useServerStream(submission: TSubmission | null) {
|
|||
overrideParentMessageId: message?.overrideParentMessageId,
|
||||
};
|
||||
createdHandler(data, { ...submission, message });
|
||||
console.log('created', message);
|
||||
} else {
|
||||
const text = data.text || data.response;
|
||||
const { initial, plugin } = data;
|
||||
if (initial) {
|
||||
console.log(data);
|
||||
}
|
||||
const { plugin, plugins } = data;
|
||||
|
||||
if (data.message) {
|
||||
messageHandler(text, { ...submission, plugin, message });
|
||||
messageHandler(text, { ...submission, plugin, plugins, message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue