mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01:00
🪄 feat: Agent Artifacts (#5804)
* refactor: remove artifacts toggle
* refactor: allow hiding side panel while allowing artifacts view
* chore: rename SidePanelGroup to SidePanel for clarity
* Revert "refactor: remove artifacts toggle"
This reverts commit f884c2cfcd.
* feat: add artifacts capability to agent configuration
* refactor: conditionally set artifacts mode based on endpoint type
* feat: Artifacts Capability for Agents
* refactor: enhance getStreamText method to handle intermediate replies and add `stream_options` for openai/azure
* feat: localize progress text and improve UX in CodeAnalyze and ExecuteCode components for expanding analysis
This commit is contained in:
parent
46f034250d
commit
bfbaaebd2b
26 changed files with 534 additions and 310 deletions
|
|
@ -1066,9 +1066,14 @@ ${convo}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStreamText() {
|
/**
|
||||||
|
*
|
||||||
|
* @param {string[]} [intermediateReply]
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getStreamText(intermediateReply) {
|
||||||
if (!this.streamHandler) {
|
if (!this.streamHandler) {
|
||||||
return '';
|
return intermediateReply?.join('') ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let thinkMatch;
|
let thinkMatch;
|
||||||
|
|
@ -1088,7 +1093,10 @@ ${convo}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const reasoningTokens = reasoningText.length > 0 ? `:::thinking\n${reasoningText}\n:::\n` : '';
|
const reasoningTokens =
|
||||||
|
reasoningText.length > 0
|
||||||
|
? `:::thinking\n${reasoningText.replace('<think>', '').replace('</think>', '').trim()}\n:::\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
return `${reasoningTokens}${this.streamHandler.tokens.join('')}`;
|
return `${reasoningTokens}${this.streamHandler.tokens.join('')}`;
|
||||||
}
|
}
|
||||||
|
|
@ -1327,11 +1335,19 @@ ${convo}
|
||||||
streamPromise = new Promise((resolve) => {
|
streamPromise = new Promise((resolve) => {
|
||||||
streamResolve = resolve;
|
streamResolve = resolve;
|
||||||
});
|
});
|
||||||
const stream = await openai.beta.chat.completions
|
/** @type {OpenAI.OpenAI.CompletionCreateParamsStreaming} */
|
||||||
.stream({
|
const params = {
|
||||||
...modelOptions,
|
...modelOptions,
|
||||||
stream: true,
|
stream: true,
|
||||||
})
|
};
|
||||||
|
if (
|
||||||
|
this.options.endpoint === EModelEndpoint.openAI ||
|
||||||
|
this.options.endpoint === EModelEndpoint.azureOpenAI
|
||||||
|
) {
|
||||||
|
params.stream_options = { include_usage: true };
|
||||||
|
}
|
||||||
|
const stream = await openai.beta.chat.completions
|
||||||
|
.stream(params)
|
||||||
.on('abort', () => {
|
.on('abort', () => {
|
||||||
/* Do nothing here */
|
/* Do nothing here */
|
||||||
})
|
})
|
||||||
|
|
@ -1471,7 +1487,7 @@ ${convo}
|
||||||
err?.message?.includes('abort') ||
|
err?.message?.includes('abort') ||
|
||||||
(err instanceof OpenAI.APIError && err?.message?.includes('abort'))
|
(err instanceof OpenAI.APIError && err?.message?.includes('abort'))
|
||||||
) {
|
) {
|
||||||
return intermediateReply.join('');
|
return this.getStreamText(intermediateReply);
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
err?.message?.includes(
|
err?.message?.includes(
|
||||||
|
|
@ -1489,7 +1505,7 @@ ${convo}
|
||||||
if (this.streamHandler && this.streamHandler.reasoningTokens.length) {
|
if (this.streamHandler && this.streamHandler.reasoningTokens.length) {
|
||||||
return this.getStreamText();
|
return this.getStreamText();
|
||||||
} else if (intermediateReply.length > 0) {
|
} else if (intermediateReply.length > 0) {
|
||||||
return intermediateReply.join('');
|
return this.getStreamText(intermediateReply);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
@ -1497,7 +1513,7 @@ ${convo}
|
||||||
if (this.streamHandler && this.streamHandler.reasoningTokens.length) {
|
if (this.streamHandler && this.streamHandler.reasoningTokens.length) {
|
||||||
return this.getStreamText();
|
return this.getStreamText();
|
||||||
} else if (intermediateReply.length > 0) {
|
} else if (intermediateReply.length > 0) {
|
||||||
return intermediateReply.join('');
|
return this.getStreamText(intermediateReply);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ const agentSchema = mongoose.Schema(
|
||||||
model_parameters: {
|
model_parameters: {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
artifacts: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
access_level: {
|
access_level: {
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const getBedrockOptions = require('~/server/services/Endpoints/bedrock/options')
|
||||||
const initOpenAI = require('~/server/services/Endpoints/openAI/initialize');
|
const initOpenAI = require('~/server/services/Endpoints/openAI/initialize');
|
||||||
const initCustom = require('~/server/services/Endpoints/custom/initialize');
|
const initCustom = require('~/server/services/Endpoints/custom/initialize');
|
||||||
const initGoogle = require('~/server/services/Endpoints/google/initialize');
|
const initGoogle = require('~/server/services/Endpoints/google/initialize');
|
||||||
|
const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
||||||
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
const { getCustomEndpointConfig } = require('~/server/services/Config');
|
||||||
const { loadAgentTools } = require('~/server/services/ToolService');
|
const { loadAgentTools } = require('~/server/services/ToolService');
|
||||||
const AgentClient = require('~/server/controllers/agents/client');
|
const AgentClient = require('~/server/controllers/agents/client');
|
||||||
|
|
@ -72,6 +73,16 @@ const primeResources = async (_attachments, _tool_resources) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} params
|
||||||
|
* @param {ServerRequest} params.req
|
||||||
|
* @param {ServerResponse} params.res
|
||||||
|
* @param {Agent} params.agent
|
||||||
|
* @param {object} [params.endpointOption]
|
||||||
|
* @param {AgentToolResources} [params.tool_resources]
|
||||||
|
* @param {boolean} [params.isInitialAgent]
|
||||||
|
* @returns {Promise<Agent>}
|
||||||
|
*/
|
||||||
const initializeAgentOptions = async ({
|
const initializeAgentOptions = async ({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
|
@ -132,6 +143,13 @@ const initializeAgentOptions = async ({
|
||||||
agent.model_parameters.model = agent.model;
|
agent.model_parameters.model = agent.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof agent.artifacts === 'string' && agent.artifacts !== '') {
|
||||||
|
agent.additional_instructions = generateArtifactsPrompt({
|
||||||
|
endpoint: agent.provider,
|
||||||
|
artifacts: agent.artifacts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const tokensModel =
|
const tokensModel =
|
||||||
agent.provider === EModelEndpoint.azureOpenAI ? agent.model : agent.model_parameters.model;
|
agent.provider === EModelEndpoint.azureOpenAI ? agent.model : agent.model_parameters.model;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ function generateConfig(key, baseURL, endpoint) {
|
||||||
config.capabilities = [
|
config.capabilities = [
|
||||||
AgentCapabilities.execute_code,
|
AgentCapabilities.execute_code,
|
||||||
AgentCapabilities.file_search,
|
AgentCapabilities.file_search,
|
||||||
|
AgentCapabilities.artifacts,
|
||||||
AgentCapabilities.actions,
|
AgentCapabilities.actions,
|
||||||
AgentCapabilities.tools,
|
AgentCapabilities.tools,
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AgentCapabilities } from 'librechat-data-provider';
|
import { AgentCapabilities, ArtifactModes } from 'librechat-data-provider';
|
||||||
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
||||||
import type { OptionWithIcon, ExtendedFile } from './types';
|
import type { OptionWithIcon, ExtendedFile } from './types';
|
||||||
|
|
||||||
|
|
@ -9,8 +9,8 @@ export type TAgentOption = OptionWithIcon &
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAgentCapabilities = {
|
export type TAgentCapabilities = {
|
||||||
[AgentCapabilities.execute_code]: boolean;
|
|
||||||
[AgentCapabilities.file_search]: boolean;
|
[AgentCapabilities.file_search]: boolean;
|
||||||
|
[AgentCapabilities.execute_code]: boolean;
|
||||||
[AgentCapabilities.end_after_tools]?: boolean;
|
[AgentCapabilities.end_after_tools]?: boolean;
|
||||||
[AgentCapabilities.hide_sequential_outputs]?: boolean;
|
[AgentCapabilities.hide_sequential_outputs]?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -26,4 +26,5 @@ export type AgentForm = {
|
||||||
tools?: string[];
|
tools?: string[];
|
||||||
provider?: AgentProvider | OptionWithIcon;
|
provider?: AgentProvider | OptionWithIcon;
|
||||||
agent_ids?: string[];
|
agent_ids?: string[];
|
||||||
|
[AgentCapabilities.artifacts]?: ArtifactModes | string;
|
||||||
} & TAgentCapabilities;
|
} & TAgentCapabilities;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ export type LastSelectedModels = Record<t.EModelEndpoint, string>;
|
||||||
|
|
||||||
export type LocalizeFunction = (
|
export type LocalizeFunction = (
|
||||||
phraseKey: TranslationKeys,
|
phraseKey: TranslationKeys,
|
||||||
options?: Record<string, string | number>
|
options?: Record<string, string | number>,
|
||||||
) => string;
|
) => string;
|
||||||
|
|
||||||
export type ChatFormValues = { text: string };
|
export type ChatFormValues = { text: string };
|
||||||
|
|
@ -89,6 +89,7 @@ export type IconMapProps = {
|
||||||
iconURL?: string;
|
iconURL?: string;
|
||||||
context?: 'landing' | 'menu-item' | 'nav' | 'message';
|
context?: 'landing' | 'menu-item' | 'nav' | 'message';
|
||||||
endpoint?: string | null;
|
endpoint?: string | null;
|
||||||
|
endpointType?: string;
|
||||||
assistantName?: string;
|
assistantName?: string;
|
||||||
agentName?: string;
|
agentName?: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function ChatView({ index = 0 }: { index?: number }) {
|
||||||
select: useCallback(
|
select: useCallback(
|
||||||
(data: TMessage[]) => {
|
(data: TMessage[]) => {
|
||||||
const dataTree = buildTree({ messages: data, fileMap });
|
const dataTree = buildTree({ messages: data, fileMap });
|
||||||
return dataTree?.length === 0 ? null : dataTree ?? null;
|
return dataTree?.length === 0 ? null : (dataTree ?? null);
|
||||||
},
|
},
|
||||||
[fileMap],
|
[fileMap],
|
||||||
),
|
),
|
||||||
|
|
@ -62,7 +62,7 @@ function ChatView({ index = 0 }: { index?: number }) {
|
||||||
<ChatFormProvider {...methods}>
|
<ChatFormProvider {...methods}>
|
||||||
<ChatContext.Provider value={chatHelpers}>
|
<ChatContext.Provider value={chatHelpers}>
|
||||||
<AddedChatContext.Provider value={addedChatHelpers}>
|
<AddedChatContext.Provider value={addedChatHelpers}>
|
||||||
<Presentation useSidePanel={true}>
|
<Presentation>
|
||||||
{content}
|
{content}
|
||||||
<div className="w-full border-t-0 pl-0 pt-2 dark:border-white/20 md:w-[calc(100%-.5rem)] md:border-t-0 md:border-transparent md:pl-0 md:pt-0 md:dark:border-transparent">
|
<div className="w-full border-t-0 pl-0 pt-2 dark:border-white/20 md:w-[calc(100%-.5rem)] md:border-t-0 md:border-transparent md:pl-0 md:pt-0 md:dark:border-transparent">
|
||||||
<ChatForm index={index} />
|
<ChatForm index={index} />
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ import FinishedIcon from './FinishedIcon';
|
||||||
import MarkdownLite from './MarkdownLite';
|
import MarkdownLite from './MarkdownLite';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
|
const radius = 56.08695652173913;
|
||||||
|
const circumference = 2 * Math.PI * radius;
|
||||||
|
|
||||||
export default function CodeAnalyze({
|
export default function CodeAnalyze({
|
||||||
initialProgress = 0.1,
|
initialProgress = 0.1,
|
||||||
code,
|
code,
|
||||||
|
|
@ -22,9 +25,6 @@ export default function CodeAnalyze({
|
||||||
const progress = useProgress(initialProgress);
|
const progress = useProgress(initialProgress);
|
||||||
const showAnalysisCode = useRecoilValue(store.showCode);
|
const showAnalysisCode = useRecoilValue(store.showCode);
|
||||||
const [showCode, setShowCode] = useState(showAnalysisCode);
|
const [showCode, setShowCode] = useState(showAnalysisCode);
|
||||||
|
|
||||||
const radius = 56.08695652173913;
|
|
||||||
const circumference = 2 * Math.PI * radius;
|
|
||||||
const offset = circumference - progress * circumference;
|
const offset = circumference - progress * circumference;
|
||||||
|
|
||||||
const logs = outputs.reduce((acc, output) => {
|
const logs = outputs.reduce((acc, output) => {
|
||||||
|
|
@ -53,9 +53,10 @@ export default function CodeAnalyze({
|
||||||
<ProgressText
|
<ProgressText
|
||||||
progress={progress}
|
progress={progress}
|
||||||
onClick={() => setShowCode((prev) => !prev)}
|
onClick={() => setShowCode((prev) => !prev)}
|
||||||
inProgressText="Analyzing"
|
inProgressText={localize('com_ui_analyzing')}
|
||||||
finishedText="Finished analyzing"
|
finishedText={localize('com_ui_analyzing_finished')}
|
||||||
hasInput={!!code.length}
|
hasInput={!!code.length}
|
||||||
|
isExpanded={showCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showCode && (
|
{showCode && (
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import type { TAttachment } from 'librechat-data-provider';
|
||||||
import ProgressText from '~/components/Chat/Messages/Content/ProgressText';
|
import ProgressText from '~/components/Chat/Messages/Content/ProgressText';
|
||||||
import FinishedIcon from '~/components/Chat/Messages/Content/FinishedIcon';
|
import FinishedIcon from '~/components/Chat/Messages/Content/FinishedIcon';
|
||||||
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
|
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
|
||||||
|
import { useProgress, useLocalize } from '~/hooks';
|
||||||
import { CodeInProgress } from './CodeProgress';
|
import { CodeInProgress } from './CodeProgress';
|
||||||
import Attachment from './Attachment';
|
import Attachment from './Attachment';
|
||||||
import LogContent from './LogContent';
|
import LogContent from './LogContent';
|
||||||
import { useProgress } from '~/hooks';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
interface ParsedArgs {
|
interface ParsedArgs {
|
||||||
|
|
@ -36,6 +36,9 @@ export function useParseArgs(args: string): ParsedArgs {
|
||||||
}, [args]);
|
}, [args]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const radius = 56.08695652173913;
|
||||||
|
const circumference = 2 * Math.PI * radius;
|
||||||
|
|
||||||
export default function ExecuteCode({
|
export default function ExecuteCode({
|
||||||
initialProgress = 0.1,
|
initialProgress = 0.1,
|
||||||
args,
|
args,
|
||||||
|
|
@ -49,14 +52,12 @@ export default function ExecuteCode({
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
attachments?: TAttachment[];
|
attachments?: TAttachment[];
|
||||||
}) {
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
const showAnalysisCode = useRecoilValue(store.showCode);
|
const showAnalysisCode = useRecoilValue(store.showCode);
|
||||||
const [showCode, setShowCode] = useState(showAnalysisCode);
|
const [showCode, setShowCode] = useState(showAnalysisCode);
|
||||||
|
|
||||||
const { lang, code } = useParseArgs(args);
|
const { lang, code } = useParseArgs(args);
|
||||||
const progress = useProgress(initialProgress);
|
const progress = useProgress(initialProgress);
|
||||||
|
|
||||||
const radius = 56.08695652173913;
|
|
||||||
const circumference = 2 * Math.PI * radius;
|
|
||||||
const offset = circumference - progress * circumference;
|
const offset = circumference - progress * circumference;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -78,9 +79,10 @@ export default function ExecuteCode({
|
||||||
<ProgressText
|
<ProgressText
|
||||||
progress={progress}
|
progress={progress}
|
||||||
onClick={() => setShowCode((prev) => !prev)}
|
onClick={() => setShowCode((prev) => !prev)}
|
||||||
inProgressText="Analyzing"
|
inProgressText={localize('com_ui_analyzing')}
|
||||||
finishedText="Finished analyzing"
|
finishedText={localize('com_ui_analyzing_finished')}
|
||||||
hasInput={!!code.length}
|
hasInput={!!code.length}
|
||||||
|
isExpanded={showCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{showCode && (
|
{showCode && (
|
||||||
|
|
@ -105,9 +107,7 @@ export default function ExecuteCode({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{attachments?.map((attachment, index) => (
|
{attachments?.map((attachment, index) => <Attachment attachment={attachment} key={index} />)}
|
||||||
<Attachment attachment={attachment} key={index} />
|
|
||||||
))}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export default function ProgressText({
|
||||||
authText,
|
authText,
|
||||||
hasInput = true,
|
hasInput = true,
|
||||||
popover = false,
|
popover = false,
|
||||||
|
isExpanded = false,
|
||||||
}: {
|
}: {
|
||||||
progress: number;
|
progress: number;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
|
@ -50,8 +51,9 @@ export default function ProgressText({
|
||||||
authText?: string;
|
authText?: string;
|
||||||
hasInput?: boolean;
|
hasInput?: boolean;
|
||||||
popover?: boolean;
|
popover?: boolean;
|
||||||
|
isExpanded?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const text = progress < 1 ? authText ?? inProgressText : finishedText;
|
const text = progress < 1 ? (authText ?? inProgressText) : finishedText;
|
||||||
return (
|
return (
|
||||||
<Wrapper popover={popover}>
|
<Wrapper popover={popover}>
|
||||||
<button
|
<button
|
||||||
|
|
@ -61,7 +63,13 @@ export default function ProgressText({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none">
|
<svg
|
||||||
|
width="16"
|
||||||
|
height="17"
|
||||||
|
viewBox="0 0 16 17"
|
||||||
|
fill="none"
|
||||||
|
className={isExpanded ? 'rotate-180' : 'rotate-0'}
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
className={hasInput ? '' : 'stroke-transparent'}
|
className={hasInput ? '' : 'stroke-transparent'}
|
||||||
d="M11.3346 7.83203L8.00131 11.1654L4.66797 7.83203"
|
d="M11.3346 7.83203L8.00131 11.1654L4.66797 7.83203"
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,19 @@
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { FileSources, LocalStorageKeys, getConfigDefaults } from 'librechat-data-provider';
|
import { FileSources, LocalStorageKeys } from 'librechat-data-provider';
|
||||||
import type { ExtendedFile } from '~/common';
|
import type { ExtendedFile } from '~/common';
|
||||||
import { useDeleteFilesMutation, useGetStartupConfig } from '~/data-provider';
|
import { useDeleteFilesMutation } from '~/data-provider';
|
||||||
import DragDropWrapper from '~/components/Chat/Input/Files/DragDropWrapper';
|
import DragDropWrapper from '~/components/Chat/Input/Files/DragDropWrapper';
|
||||||
import Artifacts from '~/components/Artifacts/Artifacts';
|
import Artifacts from '~/components/Artifacts/Artifacts';
|
||||||
import { SidePanel } from '~/components/SidePanel';
|
import { SidePanelGroup } from '~/components/SidePanel';
|
||||||
import { useSetFilesToDelete } from '~/hooks';
|
import { useSetFilesToDelete } from '~/hooks';
|
||||||
import { EditorProvider } from '~/Providers';
|
import { EditorProvider } from '~/Providers';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
const defaultInterface = getConfigDefaults().interface;
|
export default function Presentation({ children }: { children: React.ReactNode }) {
|
||||||
|
|
||||||
export default function Presentation({
|
|
||||||
children,
|
|
||||||
useSidePanel = false,
|
|
||||||
panel,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
panel?: React.ReactNode;
|
|
||||||
useSidePanel?: boolean;
|
|
||||||
}) {
|
|
||||||
const { data: startupConfig } = useGetStartupConfig();
|
|
||||||
const artifacts = useRecoilValue(store.artifactsState);
|
const artifacts = useRecoilValue(store.artifactsState);
|
||||||
const codeArtifacts = useRecoilValue(store.codeArtifacts);
|
|
||||||
const hideSidePanel = useRecoilValue(store.hideSidePanel);
|
|
||||||
const artifactsVisible = useRecoilValue(store.artifactsVisible);
|
const artifactsVisible = useRecoilValue(store.artifactsVisible);
|
||||||
|
|
||||||
const interfaceConfig = useMemo(
|
|
||||||
() => startupConfig?.interface ?? defaultInterface,
|
|
||||||
[startupConfig],
|
|
||||||
);
|
|
||||||
|
|
||||||
const setFilesToDelete = useSetFilesToDelete();
|
const setFilesToDelete = useSetFilesToDelete();
|
||||||
|
|
||||||
const { mutateAsync } = useDeleteFilesMutation({
|
const { mutateAsync } = useDeleteFilesMutation({
|
||||||
|
|
@ -83,17 +65,14 @@ export default function Presentation({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (useSidePanel && !hideSidePanel && interfaceConfig.sidePanel === true) {
|
|
||||||
return (
|
return (
|
||||||
<DragDropWrapper className="relative flex w-full grow overflow-hidden bg-presentation">
|
<DragDropWrapper className="relative flex w-full grow overflow-hidden bg-presentation">
|
||||||
<SidePanel
|
<SidePanelGroup
|
||||||
defaultLayout={defaultLayout}
|
defaultLayout={defaultLayout}
|
||||||
defaultCollapsed={defaultCollapsed}
|
|
||||||
fullPanelCollapse={fullCollapse}
|
fullPanelCollapse={fullCollapse}
|
||||||
|
defaultCollapsed={defaultCollapsed}
|
||||||
artifacts={
|
artifacts={
|
||||||
artifactsVisible === true &&
|
artifactsVisible === true && Object.keys(artifacts ?? {}).length > 0 ? (
|
||||||
codeArtifacts === true &&
|
|
||||||
Object.keys(artifacts ?? {}).length > 0 ? (
|
|
||||||
<EditorProvider>
|
<EditorProvider>
|
||||||
<Artifacts />
|
<Artifacts />
|
||||||
</EditorProvider>
|
</EditorProvider>
|
||||||
|
|
@ -103,15 +82,7 @@ export default function Presentation({
|
||||||
<main className="flex h-full flex-col overflow-y-auto" role="main">
|
<main className="flex h-full flex-col overflow-y-auto" role="main">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
</SidePanel>
|
</SidePanelGroup>
|
||||||
</DragDropWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DragDropWrapper className="relative flex w-full grow overflow-hidden bg-presentation">
|
|
||||||
{layout()}
|
|
||||||
{panel != null && panel}
|
|
||||||
</DragDropWrapper>
|
</DragDropWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import {
|
||||||
AgentCapabilities,
|
AgentCapabilities,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { TPlugin } from 'librechat-data-provider';
|
import type { TPlugin } from 'librechat-data-provider';
|
||||||
import type { AgentForm, AgentPanelProps } from '~/common';
|
import type { AgentForm, AgentPanelProps, IconComponentTypes } from '~/common';
|
||||||
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
|
import { cn, defaultTextProps, removeFocusOutlines, getEndpointField, getIconKey } from '~/utils';
|
||||||
import { useCreateAgentMutation, useUpdateAgentMutation } from '~/data-provider';
|
import { useCreateAgentMutation, useUpdateAgentMutation } from '~/data-provider';
|
||||||
import { useLocalize, useAuthContext, useHasAccess } from '~/hooks';
|
import { useLocalize, useAuthContext, useHasAccess } from '~/hooks';
|
||||||
|
|
@ -26,6 +26,7 @@ import AgentAvatar from './AgentAvatar';
|
||||||
import { Spinner } from '~/components';
|
import { Spinner } from '~/components';
|
||||||
import FileSearch from './FileSearch';
|
import FileSearch from './FileSearch';
|
||||||
import ShareAgent from './ShareAgent';
|
import ShareAgent from './ShareAgent';
|
||||||
|
import Artifacts from './Artifacts';
|
||||||
import AgentTool from './AgentTool';
|
import AgentTool from './AgentTool';
|
||||||
import CodeForm from './Code/Form';
|
import CodeForm from './Code/Form';
|
||||||
import { Panel } from '~/common';
|
import { Panel } from '~/common';
|
||||||
|
|
@ -77,6 +78,10 @@ export default function AgentConfig({
|
||||||
() => agentsConfig?.capabilities.includes(AgentCapabilities.actions),
|
() => agentsConfig?.capabilities.includes(AgentCapabilities.actions),
|
||||||
[agentsConfig],
|
[agentsConfig],
|
||||||
);
|
);
|
||||||
|
const artifactsEnabled = useMemo(
|
||||||
|
() => agentsConfig?.capabilities.includes(AgentCapabilities.artifacts) ?? false,
|
||||||
|
[agentsConfig],
|
||||||
|
);
|
||||||
const fileSearchEnabled = useMemo(
|
const fileSearchEnabled = useMemo(
|
||||||
() => agentsConfig?.capabilities.includes(AgentCapabilities.file_search) ?? false,
|
() => agentsConfig?.capabilities.includes(AgentCapabilities.file_search) ?? false,
|
||||||
[agentsConfig],
|
[agentsConfig],
|
||||||
|
|
@ -150,7 +155,7 @@ export default function AgentConfig({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
setCurrentAgentId(data.id);
|
setCurrentAgentId(data.id);
|
||||||
showToast({
|
showToast({
|
||||||
message: `${localize('com_assistants_create_success ')} ${
|
message: `${localize('com_assistants_create_success')} ${
|
||||||
data.name ?? localize('com_ui_agent')
|
data.name ?? localize('com_ui_agent')
|
||||||
}`,
|
}`,
|
||||||
});
|
});
|
||||||
|
|
@ -178,18 +183,10 @@ export default function AgentConfig({
|
||||||
}, [agent_id, setActivePanel, showToast, localize]);
|
}, [agent_id, setActivePanel, showToast, localize]);
|
||||||
|
|
||||||
const providerValue = typeof provider === 'string' ? provider : provider?.value;
|
const providerValue = typeof provider === 'string' ? provider : provider?.value;
|
||||||
|
let Icon: IconComponentTypes | null | undefined;
|
||||||
let endpointType: EModelEndpoint | undefined;
|
let endpointType: EModelEndpoint | undefined;
|
||||||
let endpointIconURL: string | undefined;
|
let endpointIconURL: string | undefined;
|
||||||
let iconKey: string | undefined;
|
let iconKey: string | undefined;
|
||||||
let Icon:
|
|
||||||
| React.ComponentType<
|
|
||||||
React.SVGProps<SVGSVGElement> & {
|
|
||||||
endpoint: string;
|
|
||||||
endpointType: EModelEndpoint | undefined;
|
|
||||||
iconURL: string | undefined;
|
|
||||||
}
|
|
||||||
>
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (providerValue !== undefined) {
|
if (providerValue !== undefined) {
|
||||||
endpointType = getEndpointField(endpointsConfig, providerValue as string, 'type');
|
endpointType = getEndpointField(endpointsConfig, providerValue as string, 'type');
|
||||||
|
|
@ -346,6 +343,8 @@ export default function AgentConfig({
|
||||||
{codeEnabled && <CodeForm agent_id={agent_id} files={code_files} />}
|
{codeEnabled && <CodeForm agent_id={agent_id} files={code_files} />}
|
||||||
{/* File Search */}
|
{/* File Search */}
|
||||||
{fileSearchEnabled && <FileSearch agent_id={agent_id} files={knowledge_files} />}
|
{fileSearchEnabled && <FileSearch agent_id={agent_id} files={knowledge_files} />}
|
||||||
|
{/* Artifacts */}
|
||||||
|
{artifactsEnabled && <Artifacts />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Agent Tools & Actions */}
|
{/* Agent Tools & Actions */}
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ export default function AgentPanel({
|
||||||
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
artifacts,
|
||||||
description,
|
description,
|
||||||
instructions,
|
instructions,
|
||||||
model: _model,
|
model: _model,
|
||||||
|
|
@ -139,6 +140,7 @@ export default function AgentPanel({
|
||||||
agent_id,
|
agent_id,
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
|
artifacts,
|
||||||
description,
|
description,
|
||||||
instructions,
|
instructions,
|
||||||
model,
|
model,
|
||||||
|
|
@ -162,6 +164,7 @@ export default function AgentPanel({
|
||||||
|
|
||||||
create.mutate({
|
create.mutate({
|
||||||
name,
|
name,
|
||||||
|
artifacts,
|
||||||
description,
|
description,
|
||||||
instructions,
|
instructions,
|
||||||
model,
|
model,
|
||||||
|
|
@ -184,7 +187,7 @@ export default function AgentPanel({
|
||||||
|
|
||||||
const canEditAgent = useMemo(() => {
|
const canEditAgent = useMemo(() => {
|
||||||
const canEdit =
|
const canEdit =
|
||||||
agentQuery.data?.isCollaborative ?? false
|
(agentQuery.data?.isCollaborative ?? false)
|
||||||
? true
|
? true
|
||||||
: agentQuery.data?.author === user?.id || user?.role === SystemRoles.ADMIN;
|
: agentQuery.data?.author === user?.id || user?.role === SystemRoles.ADMIN;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ export default function AgentSelect({
|
||||||
};
|
};
|
||||||
|
|
||||||
const capabilities: TAgentCapabilities = {
|
const capabilities: TAgentCapabilities = {
|
||||||
[AgentCapabilities.execute_code]: false,
|
|
||||||
[AgentCapabilities.file_search]: false,
|
[AgentCapabilities.file_search]: false,
|
||||||
|
[AgentCapabilities.execute_code]: false,
|
||||||
[AgentCapabilities.end_after_tools]: false,
|
[AgentCapabilities.end_after_tools]: false,
|
||||||
[AgentCapabilities.hide_sequential_outputs]: false,
|
[AgentCapabilities.hide_sequential_outputs]: false,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
124
client/src/components/SidePanel/Agents/Artifacts.tsx
Normal file
124
client/src/components/SidePanel/Agents/Artifacts.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { ArtifactModes, AgentCapabilities } from 'librechat-data-provider';
|
||||||
|
import type { AgentForm } from '~/common';
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
HoverCard,
|
||||||
|
HoverCardPortal,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from '~/components/ui';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
import { CircleHelpIcon } from '~/components/svg';
|
||||||
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
|
export default function Artifacts() {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const methods = useFormContext<AgentForm>();
|
||||||
|
const { setValue, watch } = methods;
|
||||||
|
|
||||||
|
const artifactsMode = watch(AgentCapabilities.artifacts);
|
||||||
|
|
||||||
|
const handleArtifactsChange = (value: boolean) => {
|
||||||
|
setValue(AgentCapabilities.artifacts, value ? ArtifactModes.DEFAULT : '', {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShadcnuiChange = (value: boolean) => {
|
||||||
|
setValue(AgentCapabilities.artifacts, value ? ArtifactModes.SHADCNUI : ArtifactModes.DEFAULT, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomModeChange = (value: boolean) => {
|
||||||
|
setValue(AgentCapabilities.artifacts, value ? ArtifactModes.CUSTOM : ArtifactModes.DEFAULT, {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isEnabled = artifactsMode !== undefined && artifactsMode !== '';
|
||||||
|
const isCustomEnabled = artifactsMode === ArtifactModes.CUSTOM;
|
||||||
|
const isShadcnEnabled = artifactsMode === ArtifactModes.SHADCNUI;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="mb-1.5 flex items-center gap-2">
|
||||||
|
<span>
|
||||||
|
<label className="text-token-text-primary block font-medium">
|
||||||
|
{localize('com_ui_artifacts')}
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<SwitchItem
|
||||||
|
id="artifacts"
|
||||||
|
label={localize('com_ui_artifacts_toggle_agent')}
|
||||||
|
checked={isEnabled}
|
||||||
|
onCheckedChange={handleArtifactsChange}
|
||||||
|
hoverCardText={localize('com_nav_info_code_artifacts_agent')}
|
||||||
|
/>
|
||||||
|
<SwitchItem
|
||||||
|
id="includeShadcnui"
|
||||||
|
label={localize('com_ui_include_shadcnui_agent')}
|
||||||
|
checked={isShadcnEnabled}
|
||||||
|
onCheckedChange={handleShadcnuiChange}
|
||||||
|
hoverCardText={localize('com_nav_info_include_shadcnui')}
|
||||||
|
disabled={!isEnabled || isCustomEnabled}
|
||||||
|
/>
|
||||||
|
<SwitchItem
|
||||||
|
id="customPromptMode"
|
||||||
|
label={localize('com_ui_custom_prompt_mode')}
|
||||||
|
checked={isCustomEnabled}
|
||||||
|
onCheckedChange={handleCustomModeChange}
|
||||||
|
hoverCardText={localize('com_nav_info_custom_prompt_mode')}
|
||||||
|
disabled={!isEnabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwitchItem({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
checked,
|
||||||
|
onCheckedChange,
|
||||||
|
hoverCardText,
|
||||||
|
disabled = false,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
checked: boolean;
|
||||||
|
onCheckedChange: (value: boolean) => void;
|
||||||
|
hoverCardText: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<HoverCard openDelay={50}>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className={disabled ? 'text-text-tertiary' : ''}>{label}</div>
|
||||||
|
<HoverCardTrigger>
|
||||||
|
<CircleHelpIcon className="h-4 w-4 text-text-tertiary" />
|
||||||
|
</HoverCardTrigger>
|
||||||
|
</div>
|
||||||
|
<HoverCardPortal>
|
||||||
|
<HoverCardContent side={ESide.Top} className="w-80">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm text-text-secondary">{hoverCardText}</p>
|
||||||
|
</div>
|
||||||
|
</HoverCardContent>
|
||||||
|
</HoverCardPortal>
|
||||||
|
<Switch
|
||||||
|
id={id}
|
||||||
|
checked={checked}
|
||||||
|
onCheckedChange={onCheckedChange}
|
||||||
|
className="ml-4"
|
||||||
|
data-testid={id}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HoverCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ export default function Action({ authType = '', isToolAuthenticated = false }) {
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<HoverCardTrigger>
|
<HoverCardTrigger>
|
||||||
<CircleHelpIcon className="h-5 w-5 text-gray-500" />
|
<CircleHelpIcon className="h-4 w-4 text-text-tertiary" />
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
</div>
|
</div>
|
||||||
<HoverCardPortal>
|
<HoverCardPortal>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ export default function FileSearchCheckbox() {
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center space-x-2"
|
className="flex items-center space-x-2"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
||||||
setValue(AgentCapabilities.file_search, !getValues(AgentCapabilities.file_search), {
|
setValue(AgentCapabilities.file_search, !getValues(AgentCapabilities.file_search), {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
})
|
})
|
||||||
|
|
@ -51,7 +50,7 @@ export default function FileSearchCheckbox() {
|
||||||
{localize('com_agents_enable_file_search')}
|
{localize('com_agents_enable_file_search')}
|
||||||
</label>
|
</label>
|
||||||
<HoverCardTrigger>
|
<HoverCardTrigger>
|
||||||
<CircleHelpIcon className="h-5 w-5 text-gray-500" />
|
<CircleHelpIcon className="h-4 w-4 text-text-tertiary" />
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
</button>
|
</button>
|
||||||
<HoverCardPortal>
|
<HoverCardPortal>
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,58 @@
|
||||||
import throttle from 'lodash/throttle';
|
import { useState, useCallback, useMemo, memo } from 'react';
|
||||||
import { getConfigDefaults } from 'librechat-data-provider';
|
|
||||||
import { useUserKeyQuery } from 'librechat-data-provider/react-query';
|
import { useUserKeyQuery } from 'librechat-data-provider/react-query';
|
||||||
import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react';
|
|
||||||
import type { TEndpointsConfig, TInterfaceConfig } from 'librechat-data-provider';
|
import type { TEndpointsConfig, TInterfaceConfig } from 'librechat-data-provider';
|
||||||
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||||
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
|
import { ResizableHandleAlt, ResizablePanel } from '~/components/ui/Resizable';
|
||||||
import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
|
|
||||||
import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
import { useMediaQuery, useLocalStorage, useLocalize } from '~/hooks';
|
||||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||||
|
import { useGetEndpointsQuery } from '~/data-provider';
|
||||||
import NavToggle from '~/components/Nav/NavToggle';
|
import NavToggle from '~/components/Nav/NavToggle';
|
||||||
import { cn, getEndpointField } from '~/utils';
|
import { cn, getEndpointField } from '~/utils';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import Switcher from './Switcher';
|
import Switcher from './Switcher';
|
||||||
import Nav from './Nav';
|
import Nav from './Nav';
|
||||||
|
|
||||||
interface SidePanelProps {
|
|
||||||
defaultLayout?: number[] | undefined;
|
|
||||||
defaultCollapsed?: boolean;
|
|
||||||
navCollapsedSize?: number;
|
|
||||||
fullPanelCollapse?: boolean;
|
|
||||||
artifacts?: React.ReactNode;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultMinSize = 20;
|
const defaultMinSize = 20;
|
||||||
const defaultInterface = getConfigDefaults().interface;
|
|
||||||
|
|
||||||
const normalizeLayout = (layout: number[]) => {
|
|
||||||
const sum = layout.reduce((acc, size) => acc + size, 0);
|
|
||||||
if (Math.abs(sum - 100) < 0.01) {
|
|
||||||
return layout.map((size) => Number(size.toFixed(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const factor = 100 / sum;
|
|
||||||
const normalizedLayout = layout.map((size) => Number((size * factor).toFixed(2)));
|
|
||||||
|
|
||||||
const adjustedSum = normalizedLayout.reduce(
|
|
||||||
(acc, size, index) => (index === layout.length - 1 ? acc : acc + size),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
normalizedLayout[normalizedLayout.length - 1] = Number((100 - adjustedSum).toFixed(2));
|
|
||||||
|
|
||||||
return normalizedLayout;
|
|
||||||
};
|
|
||||||
|
|
||||||
const SidePanel = ({
|
const SidePanel = ({
|
||||||
defaultLayout = [97, 3],
|
defaultSize,
|
||||||
defaultCollapsed = false,
|
panelRef,
|
||||||
fullPanelCollapse = false,
|
|
||||||
navCollapsedSize = 3,
|
navCollapsedSize = 3,
|
||||||
artifacts,
|
hasArtifacts,
|
||||||
children,
|
minSize,
|
||||||
}: SidePanelProps) => {
|
setMinSize,
|
||||||
|
collapsedSize,
|
||||||
|
setCollapsedSize,
|
||||||
|
isCollapsed,
|
||||||
|
setIsCollapsed,
|
||||||
|
fullCollapse,
|
||||||
|
setFullCollapse,
|
||||||
|
interfaceConfig,
|
||||||
|
}: {
|
||||||
|
defaultSize?: number;
|
||||||
|
hasArtifacts: boolean;
|
||||||
|
navCollapsedSize?: number;
|
||||||
|
minSize: number;
|
||||||
|
setMinSize: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
collapsedSize: number;
|
||||||
|
setCollapsedSize: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
isCollapsed: boolean;
|
||||||
|
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
fullCollapse: boolean;
|
||||||
|
setFullCollapse: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
panelRef: React.RefObject<ImperativePanelHandle>;
|
||||||
|
interfaceConfig: TInterfaceConfig;
|
||||||
|
}) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
const [minSize, setMinSize] = useState(defaultMinSize);
|
|
||||||
const [newUser, setNewUser] = useLocalStorage('newUser', true);
|
const [newUser, setNewUser] = useLocalStorage('newUser', true);
|
||||||
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
|
|
||||||
const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse);
|
|
||||||
const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize);
|
|
||||||
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
const { data: endpointsConfig = {} as TEndpointsConfig } = useGetEndpointsQuery();
|
||||||
const { data: startupConfig } = useGetStartupConfig();
|
|
||||||
const interfaceConfig = useMemo(
|
|
||||||
() => (startupConfig?.interface ?? defaultInterface) as Partial<TInterfaceConfig>,
|
|
||||||
[startupConfig],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
||||||
const { conversation } = useChatContext();
|
const { conversation } = useChatContext();
|
||||||
const { endpoint } = conversation ?? {};
|
const { endpoint } = conversation ?? {};
|
||||||
const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? '');
|
const { data: keyExpiry = { expiresAt: undefined } } = useUserKeyQuery(endpoint ?? '');
|
||||||
|
|
||||||
const panelRef = useRef<ImperativePanelHandle>(null);
|
|
||||||
|
|
||||||
const defaultActive = useMemo(() => {
|
const defaultActive = useMemo(() => {
|
||||||
const activePanel = localStorage.getItem('side:active-panel');
|
const activePanel = localStorage.getItem('side:active-panel');
|
||||||
return typeof activePanel === 'string' ? activePanel : undefined;
|
return typeof activePanel === 'string' ? activePanel : undefined;
|
||||||
|
|
@ -113,46 +93,6 @@ const SidePanel = ({
|
||||||
interfaceConfig,
|
interfaceConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
const calculateLayout = useCallback(() => {
|
|
||||||
if (artifacts == null) {
|
|
||||||
const navSize = defaultLayout.length === 2 ? defaultLayout[1] : defaultLayout[2];
|
|
||||||
return [100 - navSize, navSize];
|
|
||||||
} else {
|
|
||||||
const navSize = 0;
|
|
||||||
const remainingSpace = 100 - navSize;
|
|
||||||
const newMainSize = Math.floor(remainingSpace / 2);
|
|
||||||
const artifactsSize = remainingSpace - newMainSize;
|
|
||||||
return [newMainSize, artifactsSize, navSize];
|
|
||||||
}
|
|
||||||
}, [artifacts, defaultLayout]);
|
|
||||||
|
|
||||||
const currentLayout = useMemo(() => normalizeLayout(calculateLayout()), [calculateLayout]);
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
const throttledSaveLayout = useCallback(
|
|
||||||
throttle((sizes: number[]) => {
|
|
||||||
const normalizedSizes = normalizeLayout(sizes);
|
|
||||||
localStorage.setItem('react-resizable-panels:layout', JSON.stringify(normalizedSizes));
|
|
||||||
}, 350),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isSmallScreen) {
|
|
||||||
setIsCollapsed(true);
|
|
||||||
setCollapsedSize(0);
|
|
||||||
setMinSize(defaultMinSize);
|
|
||||||
setFullCollapse(true);
|
|
||||||
localStorage.setItem('fullPanelCollapse', 'true');
|
|
||||||
panelRef.current?.collapse();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
setIsCollapsed(defaultCollapsed);
|
|
||||||
setCollapsedSize(navCollapsedSize);
|
|
||||||
setMinSize(defaultMinSize);
|
|
||||||
}
|
|
||||||
}, [isSmallScreen, defaultCollapsed, navCollapsedSize, fullPanelCollapse]);
|
|
||||||
|
|
||||||
const toggleNavVisible = useCallback(() => {
|
const toggleNavVisible = useCallback(() => {
|
||||||
if (newUser) {
|
if (newUser) {
|
||||||
setNewUser(false);
|
setNewUser(false);
|
||||||
|
|
@ -173,36 +113,8 @@ const SidePanel = ({
|
||||||
}
|
}
|
||||||
}, [isCollapsed, newUser, setNewUser, navCollapsedSize]);
|
}, [isCollapsed, newUser, setNewUser, navCollapsedSize]);
|
||||||
|
|
||||||
const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResizablePanelGroup
|
|
||||||
direction="horizontal"
|
|
||||||
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
|
||||||
className="transition-width relative h-full w-full flex-1 overflow-auto bg-presentation"
|
|
||||||
>
|
|
||||||
<ResizablePanel
|
|
||||||
defaultSize={currentLayout[0]}
|
|
||||||
minSize={minSizeMain}
|
|
||||||
order={1}
|
|
||||||
id="messages-view"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</ResizablePanel>
|
|
||||||
{artifacts != null && (
|
|
||||||
<>
|
|
||||||
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium text-text-primary" />
|
|
||||||
<ResizablePanel
|
|
||||||
defaultSize={currentLayout[1]}
|
|
||||||
minSize={minSizeMain}
|
|
||||||
order={2}
|
|
||||||
id="artifacts-panel"
|
|
||||||
>
|
|
||||||
{artifacts}
|
|
||||||
</ResizablePanel>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => setIsHovering(true)}
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
|
|
@ -229,11 +141,11 @@ const SidePanel = ({
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
tagName="nav"
|
tagName="nav"
|
||||||
id="controls-nav"
|
id="controls-nav"
|
||||||
order={artifacts != null ? 3 : 2}
|
order={hasArtifacts != null ? 3 : 2}
|
||||||
aria-label={localize('com_ui_controls')}
|
aria-label={localize('com_ui_controls')}
|
||||||
role="region"
|
role="region"
|
||||||
collapsedSize={collapsedSize}
|
collapsedSize={collapsedSize}
|
||||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
defaultSize={defaultSize}
|
||||||
collapsible={true}
|
collapsible={true}
|
||||||
minSize={minSize}
|
minSize={minSize}
|
||||||
maxSize={40}
|
maxSize={40}
|
||||||
|
|
@ -279,21 +191,6 @@ const SidePanel = ({
|
||||||
links={Links}
|
links={Links}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
|
||||||
<button
|
|
||||||
aria-label="Close right side panel"
|
|
||||||
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
setIsCollapsed(() => {
|
|
||||||
localStorage.setItem('fullPanelCollapse', 'true');
|
|
||||||
setFullCollapse(true);
|
|
||||||
setCollapsedSize(0);
|
|
||||||
setMinSize(0);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
panelRef.current?.collapse();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
152
client/src/components/SidePanel/SidePanelGroup.tsx
Normal file
152
client/src/components/SidePanel/SidePanelGroup.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { useState, useRef, useCallback, useEffect, useMemo, memo } from 'react';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { getConfigDefaults } from 'librechat-data-provider';
|
||||||
|
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||||
|
import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/components/ui/Resizable';
|
||||||
|
import { useGetStartupConfig } from '~/data-provider';
|
||||||
|
import { normalizeLayout } from '~/utils';
|
||||||
|
import { useMediaQuery } from '~/hooks';
|
||||||
|
import SidePanel from './SidePanel';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
interface SidePanelProps {
|
||||||
|
defaultLayout?: number[] | undefined;
|
||||||
|
defaultCollapsed?: boolean;
|
||||||
|
navCollapsedSize?: number;
|
||||||
|
fullPanelCollapse?: boolean;
|
||||||
|
artifacts?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultMinSize = 20;
|
||||||
|
const defaultInterface = getConfigDefaults().interface;
|
||||||
|
|
||||||
|
const SidePanelGroup = ({
|
||||||
|
defaultLayout = [97, 3],
|
||||||
|
defaultCollapsed = false,
|
||||||
|
fullPanelCollapse = false,
|
||||||
|
navCollapsedSize = 3,
|
||||||
|
artifacts,
|
||||||
|
children,
|
||||||
|
}: SidePanelProps) => {
|
||||||
|
const { data: startupConfig } = useGetStartupConfig();
|
||||||
|
const interfaceConfig = useMemo(
|
||||||
|
() => startupConfig?.interface ?? defaultInterface,
|
||||||
|
[startupConfig],
|
||||||
|
);
|
||||||
|
|
||||||
|
const panelRef = useRef<ImperativePanelHandle>(null);
|
||||||
|
const [minSize, setMinSize] = useState(defaultMinSize);
|
||||||
|
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
|
||||||
|
const [fullCollapse, setFullCollapse] = useState(fullPanelCollapse);
|
||||||
|
const [collapsedSize, setCollapsedSize] = useState(navCollapsedSize);
|
||||||
|
|
||||||
|
const isSmallScreen = useMediaQuery('(max-width: 767px)');
|
||||||
|
const hideSidePanel = useRecoilValue(store.hideSidePanel);
|
||||||
|
|
||||||
|
const calculateLayout = useCallback(() => {
|
||||||
|
if (artifacts == null) {
|
||||||
|
const navSize = defaultLayout.length === 2 ? defaultLayout[1] : defaultLayout[2];
|
||||||
|
return [100 - navSize, navSize];
|
||||||
|
} else {
|
||||||
|
const navSize = 0;
|
||||||
|
const remainingSpace = 100 - navSize;
|
||||||
|
const newMainSize = Math.floor(remainingSpace / 2);
|
||||||
|
const artifactsSize = remainingSpace - newMainSize;
|
||||||
|
return [newMainSize, artifactsSize, navSize];
|
||||||
|
}
|
||||||
|
}, [artifacts, defaultLayout]);
|
||||||
|
|
||||||
|
const currentLayout = useMemo(() => normalizeLayout(calculateLayout()), [calculateLayout]);
|
||||||
|
|
||||||
|
const throttledSaveLayout = useCallback(
|
||||||
|
throttle((sizes: number[]) => {
|
||||||
|
const normalizedSizes = normalizeLayout(sizes);
|
||||||
|
localStorage.setItem('react-resizable-panels:layout', JSON.stringify(normalizedSizes));
|
||||||
|
}, 350),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSmallScreen) {
|
||||||
|
setIsCollapsed(true);
|
||||||
|
setCollapsedSize(0);
|
||||||
|
setMinSize(defaultMinSize);
|
||||||
|
setFullCollapse(true);
|
||||||
|
localStorage.setItem('fullPanelCollapse', 'true');
|
||||||
|
panelRef.current?.collapse();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
setIsCollapsed(defaultCollapsed);
|
||||||
|
setCollapsedSize(navCollapsedSize);
|
||||||
|
setMinSize(defaultMinSize);
|
||||||
|
}
|
||||||
|
}, [isSmallScreen, defaultCollapsed, navCollapsedSize, fullPanelCollapse]);
|
||||||
|
|
||||||
|
const minSizeMain = useMemo(() => (artifacts != null ? 15 : 30), [artifacts]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ResizablePanelGroup
|
||||||
|
direction="horizontal"
|
||||||
|
onLayout={(sizes) => throttledSaveLayout(sizes)}
|
||||||
|
className="transition-width relative h-full w-full flex-1 overflow-auto bg-presentation"
|
||||||
|
>
|
||||||
|
<ResizablePanel
|
||||||
|
defaultSize={currentLayout[0]}
|
||||||
|
minSize={minSizeMain}
|
||||||
|
order={1}
|
||||||
|
id="messages-view"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ResizablePanel>
|
||||||
|
{artifacts != null && (
|
||||||
|
<>
|
||||||
|
<ResizableHandleAlt withHandle className="ml-3 bg-border-medium text-text-primary" />
|
||||||
|
<ResizablePanel
|
||||||
|
defaultSize={currentLayout[1]}
|
||||||
|
minSize={minSizeMain}
|
||||||
|
order={2}
|
||||||
|
id="artifacts-panel"
|
||||||
|
>
|
||||||
|
{artifacts}
|
||||||
|
</ResizablePanel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!hideSidePanel && interfaceConfig.sidePanel === true && (
|
||||||
|
<SidePanel
|
||||||
|
panelRef={panelRef}
|
||||||
|
minSize={minSize}
|
||||||
|
setMinSize={setMinSize}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
setIsCollapsed={setIsCollapsed}
|
||||||
|
collapsedSize={collapsedSize}
|
||||||
|
setCollapsedSize={setCollapsedSize}
|
||||||
|
fullCollapse={fullCollapse}
|
||||||
|
setFullCollapse={setFullCollapse}
|
||||||
|
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||||
|
hasArtifacts={artifacts != null}
|
||||||
|
interfaceConfig={interfaceConfig}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ResizablePanelGroup>
|
||||||
|
<button
|
||||||
|
aria-label="Close right side panel"
|
||||||
|
className={`nav-mask ${!isCollapsed ? 'active' : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
setIsCollapsed(() => {
|
||||||
|
localStorage.setItem('fullPanelCollapse', 'true');
|
||||||
|
setFullCollapse(true);
|
||||||
|
setCollapsedSize(0);
|
||||||
|
setMinSize(0);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
panelRef.current?.collapse();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SidePanelGroup);
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export { default as SidePanel } from './SidePanel';
|
export { default as SidePanelGroup } from './SidePanelGroup';
|
||||||
export { default as SideNav } from './Nav';
|
export { default as SideNav } from './Nav';
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,10 @@ export default function useChatFunctions({
|
||||||
endpointType,
|
endpointType,
|
||||||
overrideConvoId,
|
overrideConvoId,
|
||||||
overrideUserMessageId,
|
overrideUserMessageId,
|
||||||
artifacts: getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode }),
|
artifacts:
|
||||||
|
endpoint !== EModelEndpoint.agents
|
||||||
|
? getArtifactsMode({ codeArtifacts, includeShadcnui, customPromptMode })
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
convo,
|
convo,
|
||||||
) as TEndpointOption;
|
) as TEndpointOption;
|
||||||
|
|
@ -228,7 +231,6 @@ export default function useChatFunctions({
|
||||||
conversationId,
|
conversationId,
|
||||||
unfinished: false,
|
unfinished: false,
|
||||||
isCreatedByUser: false,
|
isCreatedByUser: false,
|
||||||
isEdited: isEditOrContinue,
|
|
||||||
iconURL: convo?.iconURL,
|
iconURL: convo?.iconURL,
|
||||||
model: convo?.model,
|
model: convo?.model,
|
||||||
error: false,
|
error: false,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"com_ui_analyzing": "Analyzing",
|
||||||
|
"com_ui_analyzing_finished": "Finished analyzing",
|
||||||
"com_a11y_ai_composing": "The AI is still composing.",
|
"com_a11y_ai_composing": "The AI is still composing.",
|
||||||
"com_a11y_end": "The AI has finished their reply.",
|
"com_a11y_end": "The AI has finished their reply.",
|
||||||
"com_a11y_start": "The AI has started their reply.",
|
"com_a11y_start": "The AI has started their reply.",
|
||||||
|
|
@ -372,6 +374,7 @@
|
||||||
"com_nav_help_faq": "Help & FAQ",
|
"com_nav_help_faq": "Help & FAQ",
|
||||||
"com_nav_hide_panel": "Hide right-most side panel",
|
"com_nav_hide_panel": "Hide right-most side panel",
|
||||||
"com_nav_info_code_artifacts": "Enables the display of experimental code artifacts next to the chat",
|
"com_nav_info_code_artifacts": "Enables the display of experimental code artifacts next to the chat",
|
||||||
|
"com_nav_info_code_artifacts_agent": "Enables the use of code artifacts for this agent. By default, additional instructions specific to the use of artifacts are added, unless \"Custom Prompt Mode\" is enabled.",
|
||||||
"com_nav_info_custom_prompt_mode": "When enabled, the default artifacts system prompt will not be included. All artifact-generating instructions must be provided manually in this mode.",
|
"com_nav_info_custom_prompt_mode": "When enabled, the default artifacts system prompt will not be included. All artifact-generating instructions must be provided manually in this mode.",
|
||||||
"com_nav_info_delete_cache_storage": "This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.",
|
"com_nav_info_delete_cache_storage": "This action will delete all cached TTS (Text-to-Speech) audio files stored on your device. Cached audio files are used to speed up playback of previously generated TTS audio, but they can consume storage space on your device.",
|
||||||
"com_nav_info_enter_to_send": "When enabled, pressing `ENTER` will send your message. When disabled, pressing Enter will add a new line, and you'll need to press `CTRL + ENTER` / `⌘ + ENTER` to send your message.",
|
"com_nav_info_enter_to_send": "When enabled, pressing `ENTER` will send your message. When disabled, pressing Enter will add a new line, and you'll need to press `CTRL + ENTER` / `⌘ + ENTER` to send your message.",
|
||||||
|
|
@ -509,6 +512,7 @@
|
||||||
"com_ui_artifact_click": "Click to open",
|
"com_ui_artifact_click": "Click to open",
|
||||||
"com_ui_artifacts": "Artifacts",
|
"com_ui_artifacts": "Artifacts",
|
||||||
"com_ui_artifacts_toggle": "Toggle Artifacts UI",
|
"com_ui_artifacts_toggle": "Toggle Artifacts UI",
|
||||||
|
"com_ui_artifacts_toggle_agent": "Enable Artifacts",
|
||||||
"com_ui_ascending": "Asc",
|
"com_ui_ascending": "Asc",
|
||||||
"com_ui_assistant": "Assistant",
|
"com_ui_assistant": "Assistant",
|
||||||
"com_ui_assistant_delete_error": "There was an error deleting the assistant",
|
"com_ui_assistant_delete_error": "There was an error deleting the assistant",
|
||||||
|
|
@ -687,6 +691,7 @@
|
||||||
"com_ui_import_conversation_info": "Import conversations from a JSON file",
|
"com_ui_import_conversation_info": "Import conversations from a JSON file",
|
||||||
"com_ui_import_conversation_success": "Conversations imported successfully",
|
"com_ui_import_conversation_success": "Conversations imported successfully",
|
||||||
"com_ui_include_shadcnui": "Include shadcn/ui components instructions",
|
"com_ui_include_shadcnui": "Include shadcn/ui components instructions",
|
||||||
|
"com_ui_include_shadcnui_agent": "Include shadcn/ui instructions",
|
||||||
"com_ui_input": "Input",
|
"com_ui_input": "Input",
|
||||||
"com_ui_instructions": "Instructions",
|
"com_ui_instructions": "Instructions",
|
||||||
"com_ui_latest_footer": "Every AI for Everyone.",
|
"com_ui_latest_footer": "Every AI for Everyone.",
|
||||||
|
|
|
||||||
|
|
@ -98,3 +98,21 @@ export const extractContent = (
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const normalizeLayout = (layout: number[]) => {
|
||||||
|
const sum = layout.reduce((acc, size) => acc + size, 0);
|
||||||
|
if (Math.abs(sum - 100) < 0.01) {
|
||||||
|
return layout.map((size) => Number(size.toFixed(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const factor = 100 / sum;
|
||||||
|
const normalizedLayout = layout.map((size) => Number((size * factor).toFixed(2)));
|
||||||
|
|
||||||
|
const adjustedSum = normalizedLayout.reduce(
|
||||||
|
(acc, size, index) => (index === layout.length - 1 ? acc : acc + size),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
normalizedLayout[normalizedLayout.length - 1] = Number((100 - adjustedSum).toFixed(2));
|
||||||
|
|
||||||
|
return normalizedLayout;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import type { ZodError } from 'zod';
|
import type { ZodError } from 'zod';
|
||||||
import type { TModelsConfig } from './types';
|
import type { TModelsConfig } from './types';
|
||||||
|
|
@ -144,6 +143,7 @@ export enum AgentCapabilities {
|
||||||
end_after_tools = 'end_after_tools',
|
end_after_tools = 'end_after_tools',
|
||||||
execute_code = 'execute_code',
|
execute_code = 'execute_code',
|
||||||
file_search = 'file_search',
|
file_search = 'file_search',
|
||||||
|
artifacts = 'artifacts',
|
||||||
actions = 'actions',
|
actions = 'actions',
|
||||||
tools = 'tools',
|
tools = 'tools',
|
||||||
}
|
}
|
||||||
|
|
@ -217,6 +217,7 @@ export const agentsEndpointSChema = baseEndpointSchema.merge(
|
||||||
.default([
|
.default([
|
||||||
AgentCapabilities.execute_code,
|
AgentCapabilities.execute_code,
|
||||||
AgentCapabilities.file_search,
|
AgentCapabilities.file_search,
|
||||||
|
AgentCapabilities.artifacts,
|
||||||
AgentCapabilities.actions,
|
AgentCapabilities.actions,
|
||||||
AgentCapabilities.tools,
|
AgentCapabilities.tools,
|
||||||
]),
|
]),
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ export const defaultAgentFormValues = {
|
||||||
tools: [],
|
tools: [],
|
||||||
provider: {},
|
provider: {},
|
||||||
projectIds: [],
|
projectIds: [],
|
||||||
|
artifacts: '',
|
||||||
isCollaborative: false,
|
isCollaborative: false,
|
||||||
[Tools.execute_code]: false,
|
[Tools.execute_code]: false,
|
||||||
[Tools.file_search]: false,
|
[Tools.file_search]: false,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import type { AssistantsEndpoint, AgentProvider } from 'src/schemas';
|
||||||
import type { ContentTypes } from './runs';
|
import type { ContentTypes } from './runs';
|
||||||
import type { Agents } from './agents';
|
import type { Agents } from './agents';
|
||||||
import type { TFile } from './files';
|
import type { TFile } from './files';
|
||||||
|
import { ArtifactModes } from 'src/artifacts';
|
||||||
|
|
||||||
export type Schema = OpenAPIV3.SchemaObject & { description?: string };
|
export type Schema = OpenAPIV3.SchemaObject & { description?: string };
|
||||||
export type Reference = OpenAPIV3.ReferenceObject & { description?: string };
|
export type Reference = OpenAPIV3.ReferenceObject & { description?: string };
|
||||||
|
|
@ -204,6 +205,7 @@ export type Agent = {
|
||||||
created_at: number;
|
created_at: number;
|
||||||
avatar: AgentAvatar | null;
|
avatar: AgentAvatar | null;
|
||||||
instructions: string | null;
|
instructions: string | null;
|
||||||
|
additional_instructions?: string | null;
|
||||||
tools?: string[];
|
tools?: string[];
|
||||||
projectIds?: string[];
|
projectIds?: string[];
|
||||||
tool_kwargs?: Record<string, unknown>;
|
tool_kwargs?: Record<string, unknown>;
|
||||||
|
|
@ -217,6 +219,7 @@ export type Agent = {
|
||||||
agent_ids?: string[];
|
agent_ids?: string[];
|
||||||
end_after_tools?: boolean;
|
end_after_tools?: boolean;
|
||||||
hide_sequential_outputs?: boolean;
|
hide_sequential_outputs?: boolean;
|
||||||
|
artifacts?: ArtifactModes;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAgentsMap = Record<string, Agent | undefined>;
|
export type TAgentsMap = Record<string, Agent | undefined>;
|
||||||
|
|
@ -231,7 +234,7 @@ export type AgentCreateParams = {
|
||||||
provider: AgentProvider;
|
provider: AgentProvider;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
model_parameters: AgentModelParameters;
|
model_parameters: AgentModelParameters;
|
||||||
} & Pick<Agent, 'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs'>;
|
} & Pick<Agent, 'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs' | 'artifacts'>;
|
||||||
|
|
||||||
export type AgentUpdateParams = {
|
export type AgentUpdateParams = {
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
|
|
@ -247,7 +250,7 @@ export type AgentUpdateParams = {
|
||||||
projectIds?: string[];
|
projectIds?: string[];
|
||||||
removeProjectIds?: string[];
|
removeProjectIds?: string[];
|
||||||
isCollaborative?: boolean;
|
isCollaborative?: boolean;
|
||||||
} & Pick<Agent, 'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs'>;
|
} & Pick<Agent, 'agent_ids' | 'end_after_tools' | 'hide_sequential_outputs' | 'artifacts'>;
|
||||||
|
|
||||||
export type AgentListParams = {
|
export type AgentListParams = {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue