mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
💬 feat: assistant conversation starter (#3699)
* feat: initial UI convoStart
* fix: ConvoStarter UI
* fix: convoStarters bug
* feat: Add input field focus on conversation starters
* style: conversation starter UI update
* feat: apply fixes for starters
* style: update conversationStarters UI and fixed typo
* general UI update
* feat: Add onClick functionality to ConvoStarter component
* fix: quick fix test
* fix(AssistantSelect): remove object check
* fix: updateAssistant `conversation_starters` var
* chore: remove starter autofocus
* fix: no empty conversation starters, always show input, use Constants value for max count
* style: Update defaultTextPropsLabel styles, for a11y placeholder
* refactor: Update ConvoStarter component styles and class names for a11y and theme
* refactor: convostarter, move plus button to within persistent element
* fix: types
* chore: Update landing page assistant description styling with theming
* chore: assistant types
* refactor: documents routes
* refactor: optimize conversation starter mutations/queries
* refactor: Update listAllAssistants return type to Promise<Array<Assistant>>
* feat: edit existing starters
* feat(convo-starters): enhance ConvoStarter component and add animations
- Update ConvoStarter component styling for better visual appeal
- Implement fade-in animation for smoother appearance
- Add hover effect with background color change
- Improve text overflow handling with line-clamp and text-balance
- Ensure responsive design for various screen sizes
* feat(assistant): add conversation starters to assistant builder
- Add localization strings for conversation starters
- Update mobile.css with shake animation for max starters reached
- Enhance user experience with tooltips and dynamic input handling
* refactor: select specific fields for assistant documents fetch
* refactor: remove endpoint query key, fetch all assistant docs for now, add conversation_starters to v1 methods
* refactor: add document filters based on endpoint config
* fix: starters not applied during creation
* refactor: update AssistantSelect component to handle undefined lastSelectedModels
---------
Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
63b80c3067
commit
79f9cd5a4d
58 changed files with 602 additions and 214 deletions
|
|
@ -12,7 +12,7 @@ const Assistant = mongoose.model('assistant', assistantSchema);
|
|||
* @param {string} searchParams.user - The user ID of the assistant's author.
|
||||
* @param {Object} updateData - An object containing the properties to update.
|
||||
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
|
||||
* @returns {Promise<Object>} The updated or newly created assistant document as a plain object.
|
||||
* @returns {Promise<AssistantDocument>} The updated or newly created assistant document as a plain object.
|
||||
*/
|
||||
const updateAssistantDoc = async (searchParams, updateData, session = null) => {
|
||||
const options = { new: true, upsert: true, session };
|
||||
|
|
@ -25,7 +25,7 @@ const updateAssistantDoc = async (searchParams, updateData, session = null) => {
|
|||
* @param {Object} searchParams - The search parameters to find the assistant to update.
|
||||
* @param {string} searchParams.assistant_id - The ID of the assistant to update.
|
||||
* @param {string} searchParams.user - The user ID of the assistant's author.
|
||||
* @returns {Promise<Object|null>} The assistant document as a plain object, or null if not found.
|
||||
* @returns {Promise<AssistantDocument|null>} The assistant document as a plain object, or null if not found.
|
||||
*/
|
||||
const getAssistant = async (searchParams) => await Assistant.findOne(searchParams).lean();
|
||||
|
||||
|
|
@ -33,10 +33,17 @@ const getAssistant = async (searchParams) => await Assistant.findOne(searchParam
|
|||
* Retrieves all assistants that match the given search parameters.
|
||||
*
|
||||
* @param {Object} searchParams - The search parameters to find matching assistants.
|
||||
* @returns {Promise<Array<Object>>} A promise that resolves to an array of action documents as plain objects.
|
||||
* @param {Object} [select] - Optional. Specifies which document fields to include or exclude.
|
||||
* @returns {Promise<Array<AssistantDocument>>} A promise that resolves to an array of assistant documents as plain objects.
|
||||
*/
|
||||
const getAssistants = async (searchParams) => {
|
||||
return await Assistant.find(searchParams).lean();
|
||||
const getAssistants = async (searchParams, select = null) => {
|
||||
let query = Assistant.find(searchParams);
|
||||
|
||||
if (select) {
|
||||
query = query.select(select);
|
||||
}
|
||||
|
||||
return await query.lean();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ const assistantSchema = mongoose.Schema(
|
|||
},
|
||||
default: undefined,
|
||||
},
|
||||
conversation_starters: {
|
||||
type: [String],
|
||||
default: [],
|
||||
},
|
||||
access_level: {
|
||||
type: Number,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ const _listAssistants = async ({ req, res, version, query }) => {
|
|||
* @param {object} params.res - The response object, used for initializing the client.
|
||||
* @param {string} params.version - The API version to use.
|
||||
* @param {Omit<AssistantListParams, 'endpoint'>} params.query - The query parameters to list assistants (e.g., limit, order).
|
||||
* @returns {Promise<object>} A promise that resolves to the response from the `openai.beta.assistants.list` method call.
|
||||
* @returns {Promise<Array<Assistant>>} A promise that resolves to the response from the `openai.beta.assistants.list` method call.
|
||||
*/
|
||||
const listAllAssistants = async ({ req, res, version, query }) => {
|
||||
/** @type {{ openai: OpenAIClient }} */
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ const createAssistant = async (req, res) => {
|
|||
try {
|
||||
const { openai } = await getOpenAIClient({ req, res });
|
||||
|
||||
const { tools = [], endpoint, ...assistantData } = req.body;
|
||||
const { tools = [], endpoint, conversation_starters, ...assistantData } = req.body;
|
||||
delete assistantData.conversation_starters;
|
||||
|
||||
assistantData.tools = tools
|
||||
.map((tool) => {
|
||||
if (typeof tool !== 'string') {
|
||||
|
|
@ -41,11 +43,22 @@ const createAssistant = async (req, res) => {
|
|||
};
|
||||
|
||||
const assistant = await openai.beta.assistants.create(assistantData);
|
||||
const promise = updateAssistantDoc({ assistant_id: assistant.id }, { user: req.user.id });
|
||||
|
||||
const createData = { user: req.user.id };
|
||||
if (conversation_starters) {
|
||||
createData.conversation_starters = conversation_starters;
|
||||
}
|
||||
|
||||
const document = await updateAssistantDoc({ assistant_id: assistant.id }, createData);
|
||||
|
||||
if (azureModelIdentifier) {
|
||||
assistant.model = azureModelIdentifier;
|
||||
}
|
||||
await promise;
|
||||
|
||||
if (document.conversation_starters) {
|
||||
assistant.conversation_starters = document.conversation_starters;
|
||||
}
|
||||
|
||||
logger.debug('/assistants/', assistant);
|
||||
res.status(201).json(assistant);
|
||||
} catch (error) {
|
||||
|
|
@ -88,7 +101,7 @@ const patchAssistant = async (req, res) => {
|
|||
await validateAuthor({ req, openai });
|
||||
|
||||
const assistant_id = req.params.id;
|
||||
const { endpoint: _e, ...updateData } = req.body;
|
||||
const { endpoint: _e, conversation_starters, ...updateData } = req.body;
|
||||
updateData.tools = (updateData.tools ?? [])
|
||||
.map((tool) => {
|
||||
if (typeof tool !== 'string') {
|
||||
|
|
@ -104,6 +117,15 @@ const patchAssistant = async (req, res) => {
|
|||
}
|
||||
|
||||
const updatedAssistant = await openai.beta.assistants.update(assistant_id, updateData);
|
||||
|
||||
if (conversation_starters !== undefined) {
|
||||
const conversationStartersUpdate = await updateAssistantDoc(
|
||||
{ assistant_id },
|
||||
{ conversation_starters },
|
||||
);
|
||||
updatedAssistant.conversation_starters = conversationStartersUpdate.conversation_starters;
|
||||
}
|
||||
|
||||
res.json(updatedAssistant);
|
||||
} catch (error) {
|
||||
logger.error('[/assistants/:id] Error updating assistant', error);
|
||||
|
|
@ -153,6 +175,32 @@ const listAssistants = async (req, res) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter assistants based on configuration.
|
||||
*
|
||||
* @param {object} params - The parameters object.
|
||||
* @param {string} params.userId - The user ID to filter private assistants.
|
||||
* @param {AssistantDocument[]} params.assistants - The list of assistants to filter.
|
||||
* @param {Partial<TAssistantEndpoint>} [params.assistantsConfig] - The assistant configuration.
|
||||
* @returns {AssistantDocument[]} - The filtered list of assistants.
|
||||
*/
|
||||
function filterAssistantDocs({ documents, userId, assistantsConfig = {} }) {
|
||||
const { supportedIds, excludedIds, privateAssistants } = assistantsConfig;
|
||||
const removeUserId = (doc) => {
|
||||
const { user: _u, ...document } = doc;
|
||||
return document;
|
||||
};
|
||||
|
||||
if (privateAssistants) {
|
||||
return documents.filter((doc) => userId === doc.user.toString()).map(removeUserId);
|
||||
} else if (supportedIds?.length) {
|
||||
return documents.filter((doc) => supportedIds.includes(doc.assistant_id)).map(removeUserId);
|
||||
} else if (excludedIds?.length) {
|
||||
return documents.filter((doc) => !excludedIds.includes(doc.assistant_id)).map(removeUserId);
|
||||
}
|
||||
return documents.map(removeUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the user's assistant documents (metadata saved to database).
|
||||
* @route GET /assistants/documents
|
||||
|
|
@ -160,7 +208,25 @@ const listAssistants = async (req, res) => {
|
|||
*/
|
||||
const getAssistantDocuments = async (req, res) => {
|
||||
try {
|
||||
res.json(await getAssistants({ user: req.user.id }));
|
||||
const endpoint = req.query;
|
||||
const assistantsConfig = req.app.locals[endpoint];
|
||||
const documents = await getAssistants(
|
||||
{},
|
||||
{
|
||||
user: 1,
|
||||
assistant_id: 1,
|
||||
conversation_starters: 1,
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
},
|
||||
);
|
||||
|
||||
const docs = filterAssistantDocs({
|
||||
documents,
|
||||
userId: req.user.id,
|
||||
assistantsConfig,
|
||||
});
|
||||
res.json(docs);
|
||||
} catch (error) {
|
||||
logger.error('[/assistants/documents] Error listing assistant documents', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ const createAssistant = async (req, res) => {
|
|||
/** @type {{ openai: OpenAIClient }} */
|
||||
const { openai } = await getOpenAIClient({ req, res });
|
||||
|
||||
const { tools = [], endpoint, ...assistantData } = req.body;
|
||||
const { tools = [], endpoint, conversation_starters, ...assistantData } = req.body;
|
||||
delete assistantData.conversation_starters;
|
||||
|
||||
assistantData.tools = tools
|
||||
.map((tool) => {
|
||||
if (typeof tool !== 'string') {
|
||||
|
|
@ -39,11 +41,22 @@ const createAssistant = async (req, res) => {
|
|||
};
|
||||
|
||||
const assistant = await openai.beta.assistants.create(assistantData);
|
||||
const promise = updateAssistantDoc({ assistant_id: assistant.id }, { user: req.user.id });
|
||||
|
||||
const createData = { user: req.user.id };
|
||||
if (conversation_starters) {
|
||||
createData.conversation_starters = conversation_starters;
|
||||
}
|
||||
|
||||
const document = await updateAssistantDoc({ assistant_id: assistant.id }, createData);
|
||||
|
||||
if (azureModelIdentifier) {
|
||||
assistant.model = azureModelIdentifier;
|
||||
}
|
||||
await promise;
|
||||
|
||||
if (document.conversation_starters) {
|
||||
assistant.conversation_starters = document.conversation_starters;
|
||||
}
|
||||
|
||||
logger.debug('/assistants/', assistant);
|
||||
res.status(201).json(assistant);
|
||||
} catch (error) {
|
||||
|
|
@ -64,6 +77,17 @@ const createAssistant = async (req, res) => {
|
|||
const updateAssistant = async ({ req, openai, assistant_id, updateData }) => {
|
||||
await validateAuthor({ req, openai });
|
||||
const tools = [];
|
||||
let conversation_starters = null;
|
||||
|
||||
if (updateData?.conversation_starters) {
|
||||
const conversationStartersUpdate = await updateAssistantDoc(
|
||||
{ assistant_id: assistant_id },
|
||||
{ conversation_starters: updateData.conversation_starters },
|
||||
);
|
||||
conversation_starters = conversationStartersUpdate.conversation_starters;
|
||||
|
||||
delete updateData.conversation_starters;
|
||||
}
|
||||
|
||||
let hasFileSearch = false;
|
||||
for (const tool of updateData.tools ?? []) {
|
||||
|
|
@ -108,7 +132,13 @@ const updateAssistant = async ({ req, openai, assistant_id, updateData }) => {
|
|||
updateData.model = openai.locals.azureOptions.azureOpenAIApiDeploymentName;
|
||||
}
|
||||
|
||||
return await openai.beta.assistants.update(assistant_id, updateData);
|
||||
const assistant = await openai.beta.assistants.update(assistant_id, updateData);
|
||||
|
||||
if (conversation_starters) {
|
||||
assistant.conversation_starters = conversation_starters;
|
||||
}
|
||||
|
||||
return assistant;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
13
api/server/routes/assistants/documents.js
Normal file
13
api/server/routes/assistants/documents.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const express = require('express');
|
||||
const controllers = require('~/server/controllers/assistants/v1');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Returns a list of the user's assistant documents (metadata saved to database).
|
||||
* @route GET /assistants/documents
|
||||
* @returns {AssistantDocument[]} 200 - success response - application/json
|
||||
*/
|
||||
router.get('/', controllers.getAssistantDocuments);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
const multer = require('multer');
|
||||
const express = require('express');
|
||||
const controllers = require('~/server/controllers/assistants/v1');
|
||||
const documents = require('./documents');
|
||||
const actions = require('./actions');
|
||||
const tools = require('./tools');
|
||||
|
||||
|
|
@ -20,6 +21,13 @@ router.use('/actions', actions);
|
|||
*/
|
||||
router.use('/tools', tools);
|
||||
|
||||
/**
|
||||
* Create an assistant.
|
||||
* @route GET /assistants/documents
|
||||
* @returns {AssistantDocument[]} 200 - application/json
|
||||
*/
|
||||
router.use('/documents', documents);
|
||||
|
||||
/**
|
||||
* Create an assistant.
|
||||
* @route POST /assistants
|
||||
|
|
@ -61,13 +69,6 @@ router.delete('/:id', controllers.deleteAssistant);
|
|||
*/
|
||||
router.get('/', controllers.listAssistants);
|
||||
|
||||
/**
|
||||
* Returns a list of the user's assistant documents (metadata saved to database).
|
||||
* @route GET /assistants/documents
|
||||
* @returns {AssistantDocument[]} 200 - success response - application/json
|
||||
*/
|
||||
router.get('/documents', controllers.getAssistantDocuments);
|
||||
|
||||
/**
|
||||
* Uploads and updates an avatar for a specific assistant.
|
||||
* @route POST /avatar/:assistant_id
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const multer = require('multer');
|
|||
const express = require('express');
|
||||
const v1 = require('~/server/controllers/assistants/v1');
|
||||
const v2 = require('~/server/controllers/assistants/v2');
|
||||
const documents = require('./documents');
|
||||
const actions = require('./actions');
|
||||
const tools = require('./tools');
|
||||
|
||||
|
|
@ -21,6 +22,13 @@ router.use('/actions', actions);
|
|||
*/
|
||||
router.use('/tools', tools);
|
||||
|
||||
/**
|
||||
* Create an assistant.
|
||||
* @route GET /assistants/documents
|
||||
* @returns {AssistantDocument[]} 200 - application/json
|
||||
*/
|
||||
router.use('/documents', documents);
|
||||
|
||||
/**
|
||||
* Create an assistant.
|
||||
* @route POST /assistants
|
||||
|
|
@ -62,13 +70,6 @@ router.delete('/:id', v1.deleteAssistant);
|
|||
*/
|
||||
router.get('/', v1.listAssistants);
|
||||
|
||||
/**
|
||||
* Returns a list of the user's assistant documents (metadata saved to database).
|
||||
* @route GET /assistants/documents
|
||||
* @returns {AssistantDocument[]} 200 - success response - application/json
|
||||
*/
|
||||
router.get('/documents', v1.getAssistantDocuments);
|
||||
|
||||
/**
|
||||
* Uploads and updates an avatar for a specific assistant.
|
||||
* @route POST /avatar/:assistant_id
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export type AssistantForm = {
|
|||
name: string | null;
|
||||
description: string | null;
|
||||
instructions: string | null;
|
||||
conversation_starters: string[];
|
||||
model: string;
|
||||
functions: string[];
|
||||
} & Actions;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type {
|
|||
TConversation,
|
||||
TStartupConfig,
|
||||
EModelEndpoint,
|
||||
ActionMetadata,
|
||||
AssistantsEndpoint,
|
||||
TMessageContentParts,
|
||||
AuthorizationTypeEnum,
|
||||
|
|
@ -146,9 +147,13 @@ export type ActionAuthForm = {
|
|||
token_exchange_method: TokenExchangeMethodEnum;
|
||||
};
|
||||
|
||||
export type ActionWithNullableMetadata = Omit<Action, 'metadata'> & {
|
||||
metadata: ActionMetadata | null;
|
||||
};
|
||||
|
||||
export type AssistantPanelProps = {
|
||||
index?: number;
|
||||
action?: Action;
|
||||
action?: ActionWithNullableMetadata;
|
||||
actions?: Action[];
|
||||
assistant_id?: string;
|
||||
activePanel?: string;
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ const DeleteBookmarkButton: FC<{
|
|||
</Label>
|
||||
}
|
||||
confirm={confirmDelete}
|
||||
className="transition-color flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
className="transition-colors flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
icon={<TrashIcon className="size-4" />}
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const EditBookmarkButton: FC<{
|
|||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
className="transition-colors flex size-7 items-center justify-center rounded-lg duration-200 hover:bg-surface-hover"
|
||||
tabIndex={tabIndex}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
|
|
|
|||
17
client/src/components/Chat/ConvoStarter.tsx
Normal file
17
client/src/components/Chat/ConvoStarter.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
interface ConvoStarterProps {
|
||||
text: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export default function ConvoStarter({ text, onClick }: ConvoStarterProps) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="relative flex w-40 cursor-pointer flex-col gap-2 rounded-2xl border border-border-medium px-3 pb-4 pt-3 text-start align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition-colors duration-300 ease-in-out fade-in hover:bg-surface-tertiary"
|
||||
>
|
||||
<p className="break-word line-clamp-3 overflow-hidden text-balance break-all text-text-secondary">
|
||||
{text}
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ const ChatForm = ({ index = 0 }) => {
|
|||
newConversation={generateConversation}
|
||||
textAreaRef={textAreaRef}
|
||||
commandChar="+"
|
||||
placeholder="com_ui_add"
|
||||
placeholder="com_ui_add_model_preset"
|
||||
includeAssistants={false}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import { useMemo } from 'react';
|
||||
import { EModelEndpoint, isAssistantsEndpoint, Constants } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery, useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||
import type { ReactNode } from 'react';
|
||||
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from '~/components/ui';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { useGetAssistantDocsQuery } from '~/data-provider';
|
||||
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
|
||||
import { useLocalize, useSubmitMessage } from '~/hooks';
|
||||
import { BirthdayIcon } from '~/components/svg';
|
||||
import { getIconEndpoint, cn } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import ConvoStarter from './ConvoStarter';
|
||||
|
||||
export default function Landing({ Header }: { Header?: ReactNode }) {
|
||||
const { conversation } = useChatContext();
|
||||
|
|
@ -29,21 +32,36 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
|
||||
const iconURL = conversation?.iconURL;
|
||||
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
|
||||
const { data: documentsMap = new Map() } = useGetAssistantDocsQuery(endpoint, {
|
||||
select: (data) => new Map(data.map((dbA) => [dbA.assistant_id, dbA])),
|
||||
});
|
||||
|
||||
const isAssistant = isAssistantsEndpoint(endpoint);
|
||||
const assistant = isAssistant ? assistantMap?.[endpoint][assistant_id ?? ''] : undefined;
|
||||
const assistantName = assistant && assistant.name;
|
||||
const assistantDesc = assistant && assistant.description;
|
||||
const avatar = assistant && (assistant.metadata?.avatar as string);
|
||||
const assistantName = assistant?.name ?? '';
|
||||
const assistantDesc = assistant?.description ?? '';
|
||||
const avatar = assistant?.metadata?.avatar ?? '';
|
||||
const conversation_starters = useMemo(() => {
|
||||
/* The user made updates, use client-side cache, */
|
||||
if (assistant?.conversation_starters) {
|
||||
return assistant.conversation_starters;
|
||||
}
|
||||
/* If none in cache, we use the latest assistant docs */
|
||||
const assistantDocs = documentsMap.get(assistant_id ?? '');
|
||||
return assistantDocs?.conversation_starters ?? [];
|
||||
}, [documentsMap, assistant_id, assistant?.conversation_starters]);
|
||||
|
||||
const containerClassName =
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black';
|
||||
|
||||
const { submitMessage } = useSubmitMessage();
|
||||
const sendConversationStarter = (text: string) => submitMessage({ text });
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip>
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 right-0">{Header && Header}</div>
|
||||
<div className="absolute left-0 right-0">{Header != null ? Header : null}</div>
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<div className={cn('relative h-12 w-12', assistantName && avatar ? 'mb-0' : 'mb-3')}>
|
||||
<ConvoIcon
|
||||
|
|
@ -55,7 +73,7 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
className="h-2/3 w-2/3"
|
||||
size={41}
|
||||
/>
|
||||
{!!startupConfig?.showBirthdayIcon && (
|
||||
{startupConfig?.showBirthdayIcon === true ? (
|
||||
<div>
|
||||
<TooltipTrigger>
|
||||
<BirthdayIcon className="absolute bottom-8 right-2.5" />
|
||||
|
|
@ -64,14 +82,14 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
{localize('com_ui_happy_birthday')}
|
||||
</TooltipContent>
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
{assistantName ? (
|
||||
<div className="flex flex-col items-center gap-0 p-2">
|
||||
<div className="text-center text-2xl font-medium dark:text-white">
|
||||
{assistantName}
|
||||
</div>
|
||||
<div className="text-token-text-secondary max-w-md text-center text-xl font-normal ">
|
||||
<div className="max-w-md text-center text-sm font-normal text-text-primary ">
|
||||
{assistantDesc ? assistantDesc : localize('com_nav_welcome_message')}
|
||||
</div>
|
||||
{/* <div className="mt-1 flex items-center gap-1 text-token-text-tertiary">
|
||||
|
|
@ -85,6 +103,18 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
|
|||
: conversation?.greeting ?? localize('com_nav_welcome_message')}
|
||||
</h2>
|
||||
)}
|
||||
<div className="mt-8 flex flex-wrap justify-center gap-3 px-4">
|
||||
{conversation_starters.length > 0 &&
|
||||
conversation_starters
|
||||
.slice(0, Constants.MAX_CONVO_STARTERS)
|
||||
.map((text, index) => (
|
||||
<ConvoStarter
|
||||
key={index}
|
||||
text={text}
|
||||
onClick={() => sendConversationStarter(text)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
|||
|
||||
export default function PromptCard({ promptGroup }: { promptGroup: TPromptGroup }) {
|
||||
return (
|
||||
<div className="hover:bg-token-main-surface-secondary relative flex w-40 cursor-pointer flex-col gap-2 rounded-2xl border px-3 pb-4 pt-3 text-start align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition transition-colors duration-300 ease-in-out fade-in hover:bg-slate-100 dark:border-gray-600 dark:hover:bg-gray-700">
|
||||
<div className="hover:bg-token-main-surface-secondary relative flex w-40 cursor-pointer flex-col gap-2 rounded-2xl border px-3 pb-4 pt-3 text-start align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition-colors duration-300 ease-in-out fade-in hover:bg-slate-100 dark:border-gray-600 dark:hover:bg-gray-700">
|
||||
<div className="">
|
||||
<CategoryIcon className="size-4" category={promptGroup.category || ''} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||
import { TooltipProvider, Tooltip } from '~/components/ui';
|
||||
import ConvoIcon from '~/components/Endpoints/ConvoIcon';
|
||||
import { getIconEndpoint, cn } from '~/utils';
|
||||
import Prompts from './Prompts';
|
||||
|
||||
export default function Landing({ Header }: { Header?: ReactNode }) {
|
||||
const { conversation } = useChatContext();
|
||||
const assistantMap = useAssistantsMapContext();
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
|
||||
let { endpoint = '' } = conversation ?? {};
|
||||
const { assistant_id = null } = conversation ?? {};
|
||||
|
||||
if (
|
||||
endpoint === EModelEndpoint.chatGPTBrowser ||
|
||||
endpoint === EModelEndpoint.azureOpenAI ||
|
||||
endpoint === EModelEndpoint.gptPlugins
|
||||
) {
|
||||
endpoint = EModelEndpoint.openAI;
|
||||
}
|
||||
|
||||
const iconURL = conversation?.iconURL;
|
||||
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
|
||||
|
||||
const isAssistant = isAssistantsEndpoint(endpoint);
|
||||
const assistant = isAssistant && assistantMap?.[endpoint]?.[assistant_id ?? ''];
|
||||
const assistantName = (assistant && assistant?.name) || '';
|
||||
const avatar = (assistant && (assistant?.metadata?.avatar as string)) || '';
|
||||
|
||||
const containerClassName =
|
||||
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black';
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={50}>
|
||||
<Tooltip>
|
||||
<div className="relative h-full">
|
||||
<div className="absolute left-0 right-0">{Header && Header}</div>
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<div className={cn('relative h-12 w-12', assistantName && avatar ? 'mb-0' : 'mb-3')}>
|
||||
<ConvoIcon
|
||||
conversation={conversation}
|
||||
assistantMap={assistantMap}
|
||||
endpointsConfig={endpointsConfig}
|
||||
containerClassName={containerClassName}
|
||||
context="landing"
|
||||
className="h-2/3 w-2/3"
|
||||
size={41}
|
||||
/>
|
||||
</div>
|
||||
<div className="h-3/5">
|
||||
<Prompts />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
|
@ -149,10 +149,10 @@ export default function Conversation({
|
|||
/>
|
||||
<div className="flex gap-1">
|
||||
<button onClick={cancelRename}>
|
||||
<X className="transition-color h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
<X className="transition-colors h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
</button>
|
||||
<button onClick={onRename}>
|
||||
<Check className="transition-color h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
<Check className="transition-colors h-4 w-4 duration-200 ease-in-out hover:opacity-70" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import type {
|
|||
ValidationResult,
|
||||
AssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type { ActionAuthForm } from '~/common';
|
||||
import type { ActionAuthForm, ActionWithNullableMetadata } from '~/common';
|
||||
import type { Spec } from './ActionsTable';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
import { ActionsTable, columns } from './ActionsTable';
|
||||
|
|
@ -37,7 +37,7 @@ export default function ActionsInput({
|
|||
version,
|
||||
setAction,
|
||||
}: {
|
||||
action?: Action;
|
||||
action?: ActionWithNullableMetadata;
|
||||
assistant_id?: string;
|
||||
endpoint: AssistantsEndpoint;
|
||||
version: number | string;
|
||||
|
|
@ -62,12 +62,13 @@ export default function ActionsInput({
|
|||
const [functions, setFunctions] = useState<FunctionTool[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!action?.metadata.raw_spec) {
|
||||
const rawSpec = action?.metadata?.raw_spec ?? '';
|
||||
if (!rawSpec) {
|
||||
return;
|
||||
}
|
||||
setInputValue(action.metadata.raw_spec);
|
||||
debouncedValidation(action.metadata.raw_spec, handleResult);
|
||||
}, [action?.metadata.raw_spec]);
|
||||
setInputValue(rawSpec);
|
||||
debouncedValidation(rawSpec, handleResult);
|
||||
}, [action?.metadata?.raw_spec]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!validationResult || !validationResult.status || !validationResult.spec) {
|
||||
|
|
@ -100,7 +101,8 @@ export default function ActionsInput({
|
|||
},
|
||||
onError(error) {
|
||||
showToast({
|
||||
message: (error as Error).message ?? localize('com_assistants_update_actions_error'),
|
||||
message:
|
||||
(error as Error | undefined)?.message ?? localize('com_assistants_update_actions_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
|
|
@ -108,7 +110,8 @@ export default function ActionsInput({
|
|||
|
||||
const saveAction = handleSubmit((authFormData) => {
|
||||
console.log('authFormData', authFormData);
|
||||
if (!assistant_id) {
|
||||
const currentAssistantId = assistant_id ?? '';
|
||||
if (!currentAssistantId) {
|
||||
// alert user?
|
||||
return;
|
||||
}
|
||||
|
|
@ -121,7 +124,10 @@ export default function ActionsInput({
|
|||
return;
|
||||
}
|
||||
|
||||
let { metadata = {} } = action ?? {};
|
||||
let { metadata } = action ?? {};
|
||||
if (!metadata) {
|
||||
metadata = {};
|
||||
}
|
||||
const action_id = action?.action_id;
|
||||
metadata.raw_spec = inputValue;
|
||||
const parsedUrl = new URL(data[0].domain);
|
||||
|
|
@ -177,10 +183,10 @@ export default function ActionsInput({
|
|||
action_id,
|
||||
metadata,
|
||||
functions,
|
||||
assistant_id,
|
||||
assistant_id: currentAssistantId,
|
||||
endpoint,
|
||||
version,
|
||||
model: assistantMap?.[endpoint][assistant_id].model ?? '',
|
||||
model: assistantMap?.[endpoint][currentAssistantId].model ?? '',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ export default function ActionsPanel({
|
|||
},
|
||||
onError(error) {
|
||||
showToast({
|
||||
message: (error as Error)?.message ?? localize('com_assistants_delete_actions_error'),
|
||||
message:
|
||||
(error as Error | undefined)?.message ?? localize('com_assistants_delete_actions_error'),
|
||||
status: 'error',
|
||||
});
|
||||
},
|
||||
|
|
@ -127,7 +128,7 @@ export default function ActionsPanel({
|
|||
<div className="absolute right-0 top-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={!assistant_id || !action.action_id}
|
||||
disabled={!(assistant_id ?? '') || !action.action_id}
|
||||
className="btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium"
|
||||
>
|
||||
<TrashIcon className="text-red-500" />
|
||||
|
|
@ -145,16 +146,17 @@ export default function ActionsPanel({
|
|||
}
|
||||
selection={{
|
||||
selectHandler: () => {
|
||||
if (!assistant_id) {
|
||||
const currentId = assistant_id ?? '';
|
||||
if (!currentId) {
|
||||
return showToast({
|
||||
message: 'No assistant_id found, is the assistant created?',
|
||||
status: 'error',
|
||||
});
|
||||
}
|
||||
deleteAction.mutate({
|
||||
model: assistantMap[endpoint][assistant_id].model,
|
||||
model: assistantMap?.[endpoint][currentId].model ?? '',
|
||||
action_id: action.action_id,
|
||||
assistant_id,
|
||||
assistant_id: currentId,
|
||||
endpoint,
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default function AssistantAction({
|
|||
{isHovering && (
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<GearIcon className="icon-sm" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { Transition } from 'react-transition-group';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AssistantConversationStartersProps {
|
||||
field: {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
};
|
||||
inputClass: string;
|
||||
labelClass: string;
|
||||
}
|
||||
|
||||
const AssistantConversationStarters: React.FC<AssistantConversationStartersProps> = ({
|
||||
field,
|
||||
inputClass,
|
||||
labelClass,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
|
||||
const nodeRef = useRef(null);
|
||||
const [newStarter, setNewStarter] = useState('');
|
||||
|
||||
const handleAddStarter = () => {
|
||||
if (newStarter.trim() && field.value.length < Constants.MAX_CONVO_STARTERS) {
|
||||
const newValues = [newStarter, ...field.value];
|
||||
field.onChange(newValues);
|
||||
setNewStarter('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteStarter = (index: number) => {
|
||||
const newValues = field.value.filter((_, i) => i !== index);
|
||||
field.onChange(newValues);
|
||||
};
|
||||
const defaultStyle = {
|
||||
transition: 'opacity 200ms ease-in-out',
|
||||
opacity: 0,
|
||||
};
|
||||
|
||||
const triggerShake = (element: HTMLElement) => {
|
||||
element.classList.remove('shake');
|
||||
void element.offsetWidth;
|
||||
element.classList.add('shake');
|
||||
setTimeout(() => {
|
||||
element.classList.remove('shake');
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const transitionStyles = {
|
||||
entering: { opacity: 1 },
|
||||
entered: { opacity: 1 },
|
||||
exiting: { opacity: 0 },
|
||||
exited: { opacity: 0 },
|
||||
};
|
||||
|
||||
const hasReachedMax = field.value.length >= Constants.MAX_CONVO_STARTERS;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<label className={labelClass} htmlFor="conversation_starters">
|
||||
{localize('com_assistants_conversation_starters')}
|
||||
</label>
|
||||
<div className="mt-4 space-y-2">
|
||||
{/* Persistent starter, used for creating only */}
|
||||
<div className="relative">
|
||||
<input
|
||||
ref={(el) => (inputRefs.current[0] = el)}
|
||||
value={newStarter}
|
||||
maxLength={64}
|
||||
className={`${inputClass} pr-10`}
|
||||
type="text"
|
||||
placeholder={
|
||||
hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_assistants_conversation_starters_placeholder')
|
||||
}
|
||||
onChange={(e) => setNewStarter(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (hasReachedMax) {
|
||||
triggerShake(e.currentTarget);
|
||||
} else {
|
||||
handleAddStarter();
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Transition
|
||||
nodeRef={nodeRef}
|
||||
in={field.value.length < Constants.MAX_CONVO_STARTERS}
|
||||
timeout={200}
|
||||
unmountOnExit
|
||||
>
|
||||
{(state: string) => (
|
||||
<div
|
||||
ref={nodeRef}
|
||||
style={{
|
||||
...defaultStyle,
|
||||
...transitionStyles[state as keyof typeof transitionStyles],
|
||||
transition: state === 'entering' ? 'none' : defaultStyle.transition,
|
||||
}}
|
||||
className="absolute right-1 top-1"
|
||||
>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={handleAddStarter}
|
||||
disabled={hasReachedMax}
|
||||
>
|
||||
<Plus className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{hasReachedMax
|
||||
? localize('com_assistants_max_starters_reached')
|
||||
: localize('com_ui_add')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
{field.value.map((starter, index) => (
|
||||
<div key={index} className="relative">
|
||||
<input
|
||||
ref={(el) => (inputRefs.current[index + 1] = el)}
|
||||
value={starter}
|
||||
onChange={(e) => {
|
||||
const newValue = [...field.value];
|
||||
newValue[index] = e.target.value;
|
||||
field.onChange(newValue);
|
||||
}}
|
||||
className={`${inputClass} pr-10`}
|
||||
type="text"
|
||||
maxLength={64}
|
||||
/>
|
||||
<TooltipProvider delayDuration={1000}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute right-1 top-1 flex size-7 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-hover"
|
||||
onClick={() => handleDeleteStarter(index)}
|
||||
>
|
||||
<X className="size-4" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AssistantConversationStarters;
|
||||
|
|
@ -14,6 +14,7 @@ import type { FunctionTool, TConfig, TPlugin } from 'librechat-data-provider';
|
|||
import type { AssistantForm, AssistantPanelProps } from '~/common';
|
||||
import { useCreateAssistantMutation, useUpdateAssistantMutation } from '~/data-provider';
|
||||
import { cn, cardStyle, defaultTextProps, removeFocusOutlines } from '~/utils';
|
||||
import AssistantConversationStarters from './AssistantConversationStarters';
|
||||
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||
import { useSelectAssistant, useLocalize } from '~/hooks';
|
||||
import { ToolSelectDialog } from '~/components/Tools';
|
||||
|
|
@ -31,7 +32,7 @@ import { Panel } from '~/common';
|
|||
const labelClass = 'mb-2 text-token-text-primary block font-medium';
|
||||
const inputClass = cn(
|
||||
defaultTextProps,
|
||||
'flex w-full px-3 py-2 dark:border-gray-800 dark:bg-gray-800',
|
||||
'flex w-full px-3 py-2 dark:border-gray-800 dark:bg-gray-800 rounded-xl mb-2',
|
||||
removeFocusOutlines,
|
||||
);
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ export default function AssistantPanel({
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
const create = useCreateAssistantMutation({
|
||||
onSuccess: (data) => {
|
||||
setCurrentAssistantId(data.id);
|
||||
|
|
@ -139,7 +141,7 @@ export default function AssistantPanel({
|
|||
return functionName;
|
||||
} else {
|
||||
const assistant = assistantMap?.[endpoint]?.[assistant_id];
|
||||
const tool = assistant?.tools.find((tool) => tool.function?.name === functionName);
|
||||
const tool = assistant?.tools?.find((tool) => tool.function?.name === functionName);
|
||||
if (assistant && tool) {
|
||||
return tool;
|
||||
}
|
||||
|
|
@ -148,7 +150,6 @@ export default function AssistantPanel({
|
|||
return functionName;
|
||||
});
|
||||
|
||||
console.log(data);
|
||||
if (data.code_interpreter) {
|
||||
tools.push({ type: Tools.code_interpreter });
|
||||
}
|
||||
|
|
@ -163,6 +164,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters,
|
||||
model,
|
||||
// file_ids, // TODO: add file handling here
|
||||
} = data;
|
||||
|
|
@ -174,6 +176,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters.filter((starter) => starter.trim() !== ''),
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
|
|
@ -186,6 +189,7 @@ export default function AssistantPanel({
|
|||
name,
|
||||
description,
|
||||
instructions,
|
||||
conversation_starters: starters.filter((starter) => starter.trim() !== ''),
|
||||
model,
|
||||
tools,
|
||||
endpoint,
|
||||
|
|
@ -239,12 +243,12 @@ export default function AssistantPanel({
|
|||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-auto bg-white px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
<div className="bg-surface-50 h-auto px-4 pb-8 pt-3 dark:bg-transparent">
|
||||
{/* Avatar & Name */}
|
||||
<div className="mb-4">
|
||||
<AssistantAvatar
|
||||
createMutation={create}
|
||||
assistant_id={assistant_id ?? null}
|
||||
assistant_id={assistant_id}
|
||||
metadata={assistant['metadata'] ?? null}
|
||||
endpoint={endpoint}
|
||||
version={version}
|
||||
|
|
@ -271,7 +275,7 @@ export default function AssistantPanel({
|
|||
name="id"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<p className="h-3 text-xs italic text-text-secondary">{field.value ?? ''}</p>
|
||||
<p className="h-3 text-xs italic text-text-secondary">{field.value}</p>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -318,6 +322,23 @@ export default function AssistantPanel({
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Conversation Starters */}
|
||||
<div className="relative mb-6">
|
||||
{/* the label of conversation starters is in the component */}
|
||||
<Controller
|
||||
name="conversation_starters"
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ field }) => (
|
||||
<AssistantConversationStarters
|
||||
field={field}
|
||||
inputClass={inputClass}
|
||||
labelClass={labelClass}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{/* Model */}
|
||||
<div className="mb-6">
|
||||
<label className={labelClass} htmlFor="model">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ import {
|
|||
} from 'librechat-data-provider';
|
||||
import type { UseFormReset } from 'react-hook-form';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type { Assistant, AssistantCreateParams, AssistantsEndpoint } from 'librechat-data-provider';
|
||||
import type {
|
||||
Assistant,
|
||||
AssistantCreateParams,
|
||||
AssistantDocument,
|
||||
AssistantsEndpoint,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
Actions,
|
||||
ExtendedFile,
|
||||
|
|
@ -19,13 +24,20 @@ import type {
|
|||
TAssistantOption,
|
||||
LastSelectedModels,
|
||||
} from '~/common';
|
||||
import { useListAssistantsQuery, useGetAssistantDocsQuery } from '~/data-provider';
|
||||
import SelectDropDown from '~/components/ui/SelectDropDown';
|
||||
import { useListAssistantsQuery } from '~/data-provider';
|
||||
import { useLocalize, useLocalStorage } from '~/hooks';
|
||||
import { useFileMapContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const keys = new Set(['name', 'id', 'description', 'instructions', 'model']);
|
||||
const keys = new Set([
|
||||
'name',
|
||||
'id',
|
||||
'description',
|
||||
'instructions',
|
||||
'conversation_starters',
|
||||
'model',
|
||||
]);
|
||||
|
||||
export default function AssistantSelect({
|
||||
reset,
|
||||
|
|
@ -45,11 +57,18 @@ export default function AssistantSelect({
|
|||
const localize = useLocalize();
|
||||
const fileMap = useFileMapContext();
|
||||
const lastSelectedAssistant = useRef<string | null>(null);
|
||||
const [lastSelectedModels] = useLocalStorage<LastSelectedModels>(
|
||||
const [lastSelectedModels] = useLocalStorage<LastSelectedModels | undefined>(
|
||||
LocalStorageKeys.LAST_MODEL,
|
||||
{} as LastSelectedModels,
|
||||
);
|
||||
|
||||
const { data: documentsMap = new Map<string, AssistantDocument>() } = useGetAssistantDocsQuery(
|
||||
endpoint,
|
||||
{
|
||||
select: (data) => new Map(data.map((dbA) => [dbA.assistant_id, dbA])),
|
||||
},
|
||||
);
|
||||
|
||||
const assistants = useListAssistantsQuery(endpoint, undefined, {
|
||||
select: (res) =>
|
||||
res.data.map((_assistant) => {
|
||||
|
|
@ -57,10 +76,10 @@ export default function AssistantSelect({
|
|||
endpoint === EModelEndpoint.assistants ? FileSources.openai : FileSources.azure;
|
||||
const assistant = {
|
||||
..._assistant,
|
||||
label: _assistant?.name ?? '',
|
||||
label: _assistant.name ?? '',
|
||||
value: _assistant.id,
|
||||
files: _assistant?.file_ids ? ([] as Array<[string, ExtendedFile]>) : undefined,
|
||||
code_files: _assistant?.tool_resources?.code_interpreter?.file_ids
|
||||
files: _assistant.file_ids ? ([] as Array<[string, ExtendedFile]>) : undefined,
|
||||
code_files: _assistant.tool_resources?.code_interpreter?.file_ids
|
||||
? ([] as Array<[string, ExtendedFile]>)
|
||||
: undefined,
|
||||
};
|
||||
|
|
@ -104,11 +123,17 @@ export default function AssistantSelect({
|
|||
}
|
||||
|
||||
if (assistant.code_files && _assistant.tool_resources?.code_interpreter?.file_ids) {
|
||||
_assistant.tool_resources?.code_interpreter?.file_ids?.forEach((file_id) =>
|
||||
_assistant.tool_resources.code_interpreter.file_ids.forEach((file_id) =>
|
||||
handleFile(file_id, assistant.code_files),
|
||||
);
|
||||
}
|
||||
|
||||
const assistantDoc = documentsMap.get(_assistant.id);
|
||||
/* If no user updates, use the latest assistant docs */
|
||||
if (assistantDoc && !assistant.conversation_starters) {
|
||||
assistant.conversation_starters = assistantDoc.conversation_starters;
|
||||
}
|
||||
|
||||
return assistant;
|
||||
}),
|
||||
});
|
||||
|
|
@ -128,8 +153,8 @@ export default function AssistantSelect({
|
|||
|
||||
const update = {
|
||||
...assistant,
|
||||
label: assistant?.name ?? '',
|
||||
value: assistant?.id ?? '',
|
||||
label: assistant.name ?? '',
|
||||
value: assistant.id ?? '',
|
||||
};
|
||||
|
||||
const actions: Actions = {
|
||||
|
|
@ -138,9 +163,9 @@ export default function AssistantSelect({
|
|||
[Capabilities.retrieval]: false,
|
||||
};
|
||||
|
||||
assistant?.tools
|
||||
?.filter((tool) => tool.type !== 'function' || isImageVisionTool(tool))
|
||||
?.map((tool) => tool?.function?.name || tool.type)
|
||||
(assistant.tools ?? [])
|
||||
.filter((tool) => tool.type !== 'function' || isImageVisionTool(tool))
|
||||
.map((tool) => tool.function?.name || tool.type)
|
||||
.forEach((tool) => {
|
||||
if (tool === Tools.file_search) {
|
||||
actions[Capabilities.retrieval] = true;
|
||||
|
|
@ -148,10 +173,9 @@ export default function AssistantSelect({
|
|||
actions[tool] = true;
|
||||
});
|
||||
|
||||
const functions =
|
||||
assistant?.tools
|
||||
?.filter((tool) => tool.type === 'function' && !isImageVisionTool(tool))
|
||||
?.map((tool) => tool.function?.name ?? '') ?? [];
|
||||
const functions = (assistant.tools ?? [])
|
||||
.filter((tool) => tool.type === 'function' && !isImageVisionTool(tool))
|
||||
.map((tool) => tool.function?.name ?? '');
|
||||
|
||||
const formValues: Partial<AssistantForm & Actions> = {
|
||||
functions,
|
||||
|
|
@ -161,18 +185,26 @@ export default function AssistantSelect({
|
|||
};
|
||||
|
||||
Object.entries(assistant).forEach(([name, value]) => {
|
||||
if (typeof value === 'number') {
|
||||
return;
|
||||
} else if (typeof value === 'object') {
|
||||
if (!keys.has(name)) {
|
||||
return;
|
||||
}
|
||||
if (keys.has(name)) {
|
||||
|
||||
if (
|
||||
name === 'conversation_starters' &&
|
||||
Array.isArray(value) &&
|
||||
value.every((item) => typeof item === 'string')
|
||||
) {
|
||||
formValues[name] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value !== 'number' && typeof value !== 'object') {
|
||||
formValues[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
reset(formValues);
|
||||
setCurrentAssistantId(assistant?.id);
|
||||
setCurrentAssistantId(assistant.id);
|
||||
},
|
||||
[assistants.data, reset, setCurrentAssistantId, createMutation, endpoint, lastSelectedModels],
|
||||
);
|
||||
|
|
@ -184,7 +216,7 @@ export default function AssistantSelect({
|
|||
return;
|
||||
}
|
||||
|
||||
if (selectedAssistant && assistants.data) {
|
||||
if (selectedAssistant !== '' && selectedAssistant != null && assistants.data) {
|
||||
timerId = setTimeout(() => {
|
||||
lastSelectedAssistant.current = selectedAssistant;
|
||||
onSelect(selectedAssistant);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default function AssistantTool({
|
|||
<OGDialogTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="transition-color flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<TrashIcon />
|
||||
</button>
|
||||
|
|
@ -97,7 +97,7 @@ export default function AssistantTool({
|
|||
selection={{
|
||||
selectHandler: () => removeTool(currentTool.pluginKey),
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-color duration-200 text-white',
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 transition-colors duration-200 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ function ToolItem({ tool, onAddTool, onRemoveTool, isInstalled }: ToolItemProps)
|
|||
{!isInstalled ? (
|
||||
<button
|
||||
className="btn btn-primary relative"
|
||||
aria-label={`${localize('com_nav_tool_add')} ${tool.name}`}
|
||||
aria-label={`${localize('com_ui_add')} ${tool.name}`}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="flex w-full items-center justify-center gap-2">
|
||||
{localize('com_nav_tool_add')}
|
||||
{localize('com_ui_add')}
|
||||
<PlusCircleIcon className="flex h-4 w-4 items-center stroke-2" />
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -622,7 +622,7 @@ export const useUploadFileMutation = (
|
|||
|
||||
const update = {};
|
||||
if (!tool_resource) {
|
||||
update['file_ids'] = [...assistant.file_ids, data.file_id];
|
||||
update['file_ids'] = [...(assistant.file_ids ?? []), data.file_id];
|
||||
}
|
||||
if (tool_resource === EToolResources.code_interpreter) {
|
||||
const prevResources = assistant.tool_resources ?? {};
|
||||
|
|
@ -884,6 +884,24 @@ export const useUpdateAssistantMutation = (
|
|||
return options?.onSuccess?.(updatedAssistant, variables, context);
|
||||
}
|
||||
|
||||
queryClient.setQueryData<t.AssistantDocument[]>(
|
||||
[QueryKeys.assistantDocs, variables.data.endpoint],
|
||||
(prev) => {
|
||||
if (!prev) {
|
||||
return prev;
|
||||
}
|
||||
prev.map((doc) => {
|
||||
if (doc.assistant_id === variables.assistant_id) {
|
||||
return {
|
||||
...doc,
|
||||
conversation_starters: updatedAssistant.conversation_starters,
|
||||
};
|
||||
}
|
||||
return doc;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
queryClient.setQueryData<t.AssistantListResponse>(
|
||||
[QueryKeys.assistants, variables.data.endpoint, defaultOrderQuery],
|
||||
{
|
||||
|
|
@ -1070,7 +1088,7 @@ export const useDeleteAction = (
|
|||
if (assistant.id === variables.assistant_id) {
|
||||
return {
|
||||
...assistant,
|
||||
tools: assistant.tools.filter(
|
||||
tools: (assistant.tools ?? []).filter(
|
||||
(tool) => !tool.function?.name.includes(domain ?? ''),
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -249,8 +249,8 @@ export const useListAssistantsQuery = <TData = AssistantListResponse>(
|
|||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const userProvidesKey = !!(endpointsConfig?.[endpoint]?.userProvide ?? false);
|
||||
const keyProvided = userProvidesKey ? !!(keyExpiry?.expiresAt ?? '') : true;
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<AssistantListResponse, unknown, TData>(
|
||||
|
|
@ -368,22 +368,24 @@ export const useGetActionsQuery = <TData = Action[]>(
|
|||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for retrieving user's saved Assistant Documents (metadata saved to Database)
|
||||
*/
|
||||
export const useGetAssistantDocsQuery = (
|
||||
endpoint: t.AssistantsEndpoint,
|
||||
config?: UseQueryOptions<AssistantDocument[]>,
|
||||
): QueryObserverResult<AssistantDocument[], unknown> => {
|
||||
export const useGetAssistantDocsQuery = <TData = AssistantDocument[]>(
|
||||
endpoint: t.AssistantsEndpoint | string,
|
||||
config?: UseQueryOptions<AssistantDocument[], unknown, TData>,
|
||||
): QueryObserverResult<TData> => {
|
||||
const queryClient = useQueryClient();
|
||||
const endpointsConfig = queryClient.getQueryData<TEndpointsConfig>([QueryKeys.endpoints]);
|
||||
const keyExpiry = queryClient.getQueryData<TCheckUserKeyResponse>([QueryKeys.name, endpoint]);
|
||||
const userProvidesKey = !!endpointsConfig?.[endpoint]?.userProvide;
|
||||
const keyProvided = userProvidesKey ? !!keyExpiry?.expiresAt : true;
|
||||
const userProvidesKey = !!(endpointsConfig?.[endpoint]?.userProvide ?? false);
|
||||
const keyProvided = userProvidesKey ? !!(keyExpiry?.expiresAt ?? '') : true;
|
||||
const enabled = !!endpointsConfig?.[endpoint] && keyProvided;
|
||||
const version = endpointsConfig?.[endpoint]?.version ?? defaultAssistantsVersion[endpoint];
|
||||
return useQuery<AssistantDocument[]>(
|
||||
[QueryKeys.assistantDocs],
|
||||
|
||||
return useQuery<AssistantDocument[], unknown, TData>(
|
||||
[QueryKeys.assistantDocs, endpoint],
|
||||
() =>
|
||||
dataService.getAssistantDocs({
|
||||
endpoint,
|
||||
|
|
|
|||
|
|
@ -549,7 +549,7 @@ export default {
|
|||
com_nav_change_picture: 'تغيير الصورة',
|
||||
com_nav_plugin_install: 'تثبيت',
|
||||
com_nav_plugin_uninstall: 'إلغاء تثبيت',
|
||||
com_nav_tool_add: 'إضافة',
|
||||
com_ui_add: 'إضافة',
|
||||
com_nav_tool_remove: 'إزالة',
|
||||
com_nav_tool_dialog: 'أدوات المساعد',
|
||||
com_nav_tool_dialog_description: 'يجب حفظ المساعد لإبقاء اختيارات الأدوات.',
|
||||
|
|
@ -2594,7 +2594,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'إلغاء تثبيت',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'إضافة',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ export default {
|
|||
com_nav_plugin_store: 'Loja de plugins',
|
||||
com_nav_plugin_install: 'Instalar',
|
||||
com_nav_plugin_uninstall: 'Desinstalar',
|
||||
com_nav_tool_add: 'Adicionar',
|
||||
com_ui_add: 'Adicionar',
|
||||
com_nav_tool_remove: 'Remover',
|
||||
com_nav_tool_dialog: 'Ferramentas do Assistente',
|
||||
com_nav_tool_dialog_description:
|
||||
|
|
@ -2021,7 +2021,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'Desinstalar',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Adicionar',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ export default {
|
|||
com_ui_fork_from_message: 'Wähle eine Abzweigungsoption',
|
||||
com_ui_mention:
|
||||
'Erwähne einen Endpunkt, Assistenten oder eine Voreinstellung, um schnell dorthin zu wechseln',
|
||||
com_ui_add: 'Ein KI-Modell oder eine Voreinstellung für eine zusätzliche Antwort hinzufügen',
|
||||
com_ui_add_model_preset: 'Ein KI-Modell oder eine Voreinstellung für eine zusätzliche Antwort hinzufügen',
|
||||
com_ui_regenerate: 'Neu generieren',
|
||||
com_ui_continue: 'Fortfahren',
|
||||
com_ui_edit: 'Bearbeiten',
|
||||
|
|
@ -592,7 +592,7 @@ export default {
|
|||
com_nav_plugin_store: 'Plugin-Store',
|
||||
com_nav_plugin_install: 'Installieren',
|
||||
com_nav_plugin_uninstall: 'Deinstallieren',
|
||||
com_nav_tool_add: 'Hinzufügen',
|
||||
com_ui_add: 'Hinzufügen',
|
||||
com_nav_tool_remove: 'Entfernen',
|
||||
com_nav_tool_dialog: 'Assistenten-Werkzeuge',
|
||||
com_ui_misc: 'Sonstiges',
|
||||
|
|
@ -2283,7 +2283,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'Deinstallieren',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Hinzufügen',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -78,6 +78,8 @@ export default {
|
|||
com_assistants_update_error: 'There was an error updating your assistant.',
|
||||
com_assistants_create_success: 'Successfully created',
|
||||
com_assistants_create_error: 'There was an error creating your assistant.',
|
||||
com_assistants_conversation_starters: 'Conversation Starters',
|
||||
com_assistants_conversation_starters_placeholder: 'Enter a conversation starter',
|
||||
com_ui_date_today: 'Today',
|
||||
com_ui_date_yesterday: 'Yesterday',
|
||||
com_ui_date_previous_7_days: 'Previous 7 days',
|
||||
|
|
@ -221,7 +223,8 @@ export default {
|
|||
com_ui_fork_visible: 'Visible messages only',
|
||||
com_ui_fork_from_message: 'Select a fork option',
|
||||
com_ui_mention: 'Mention an endpoint, assistant, or preset to quickly switch to it',
|
||||
com_ui_add: 'Add a model or preset for an additional response',
|
||||
com_ui_add_model_preset: 'Add a model or preset for an additional response',
|
||||
com_assistants_max_starters_reached: 'Max number of conversation starters reached',
|
||||
com_ui_regenerate: 'Regenerate',
|
||||
com_ui_continue: 'Continue',
|
||||
com_ui_edit: 'Edit',
|
||||
|
|
@ -603,7 +606,7 @@ export default {
|
|||
com_nav_plugin_store: 'Plugin store',
|
||||
com_nav_plugin_install: 'Install',
|
||||
com_nav_plugin_uninstall: 'Uninstall',
|
||||
com_nav_tool_add: 'Add',
|
||||
com_ui_add: 'Add',
|
||||
com_nav_tool_remove: 'Remove',
|
||||
com_nav_tool_dialog: 'Assistant Tools',
|
||||
com_ui_misc: 'Misc.',
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ export default {
|
|||
com_nav_plugin_store: 'Tienda de plugins',
|
||||
com_nav_plugin_install: 'Instalar',
|
||||
com_nav_plugin_uninstall: 'Desinstalar',
|
||||
com_nav_tool_add: 'Agregar',
|
||||
com_ui_add: 'Agregar',
|
||||
com_nav_tool_remove: 'Eliminar',
|
||||
com_nav_tool_dialog: 'Herramientas del asistente',
|
||||
com_nav_tool_dialog_description:
|
||||
|
|
@ -2153,7 +2153,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'Desinstalar',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Agregar',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ export default {
|
|||
com_ui_fork_visible: 'Vain näkyvät viestit',
|
||||
com_ui_fork_from_message: 'Valitse haarautustapa',
|
||||
com_ui_mention: 'Mainitse päätepiste, Avustaja tai asetus vaihtaaksesi siihen pikana',
|
||||
com_ui_add: 'Lisää malli tai esiasetus lisävastausta varten',
|
||||
com_ui_add_model_preset: 'Lisää malli tai esiasetus lisävastausta varten',
|
||||
com_ui_regenerate: 'Luo uudestaan',
|
||||
com_ui_continue: 'Jatka',
|
||||
com_ui_edit: 'Muokkaa',
|
||||
|
|
@ -571,7 +571,7 @@ export default {
|
|||
com_nav_plugin_store: 'Lisäosakauppa',
|
||||
com_nav_plugin_install: 'Asenna',
|
||||
com_nav_plugin_uninstall: 'Poista',
|
||||
com_nav_tool_add: 'Lisää',
|
||||
com_ui_add: 'Lisää',
|
||||
com_nav_tool_remove: 'Poista',
|
||||
com_nav_tool_dialog: 'Avustajatyökalut',
|
||||
com_ui_misc: 'Muu',
|
||||
|
|
|
|||
|
|
@ -418,7 +418,7 @@ export default {
|
|||
com_ui_date_december: 'Décembre',
|
||||
com_ui_nothing_found: 'Aucun résultat trouvé',
|
||||
com_ui_go_to_conversation: 'Aller à la conversation',
|
||||
com_nav_tool_add: 'Ajouter',
|
||||
com_ui_add: 'Ajouter',
|
||||
com_nav_tool_remove: 'Supprimer',
|
||||
com_nav_tool_dialog: 'Outils de l\'assistant',
|
||||
com_nav_tool_dialog_description:
|
||||
|
|
@ -624,7 +624,7 @@ export default {
|
|||
com_ui_upload_invalid_var:
|
||||
'Fichier non valide pour le téléchargement. L\'image ne doit pas dépasser {0} Mo',
|
||||
com_ui_read_aloud: 'Lire à haute voix',
|
||||
com_ui_add: 'Ajouter un modèle ou un préréglage pour une réponse supplémentaire',
|
||||
com_ui_add_model_preset: 'Ajouter un modèle ou un préréglage pour une réponse supplémentaire',
|
||||
com_ui_loading: 'Chargement...',
|
||||
com_ui_all_proper: 'Tout',
|
||||
com_ui_chat: 'Discussion',
|
||||
|
|
@ -2208,7 +2208,7 @@ export const comparisons = {
|
|||
english: 'Go to conversation',
|
||||
translated: 'Aller à la conversation',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Ajouter',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -354,7 +354,7 @@ export default {
|
|||
com_nav_plugin_store: 'חנות פלאגין',
|
||||
com_nav_plugin_install: 'התקן',
|
||||
com_nav_plugin_uninstall: 'הסר התקנה',
|
||||
com_nav_tool_add: 'הוסף',
|
||||
com_ui_add: 'הוסף',
|
||||
com_nav_tool_remove: 'הסר',
|
||||
com_nav_tool_dialog: 'כלי סייען',
|
||||
com_nav_tool_dialog_description: 'יש לשמור את האסיסטנט כדי להמשיך בבחירת הכלים.',
|
||||
|
|
@ -1728,7 +1728,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'הסר התקנה',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'הוסף',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ export default {
|
|||
com_nav_plugin_store: 'Store plugin',
|
||||
com_nav_plugin_install: 'Installa',
|
||||
com_nav_plugin_uninstall: 'Disinstalla',
|
||||
com_nav_tool_add: 'Aggiungi',
|
||||
com_ui_add: 'Aggiungi',
|
||||
com_nav_tool_remove: 'Rimuovi',
|
||||
com_nav_tool_dialog: 'Strumenti Assistente',
|
||||
com_nav_tool_dialog_description:
|
||||
|
|
@ -2326,7 +2326,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'Disinstalla',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Aggiungi',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -431,7 +431,7 @@ export default {
|
|||
com_nav_plugin_store: 'プラグインストア',
|
||||
com_nav_plugin_install: 'インストール',
|
||||
com_nav_plugin_uninstall: 'アンインストール',
|
||||
com_nav_tool_add: '追加',
|
||||
com_ui_add: '追加',
|
||||
com_nav_tool_dialog: 'アシスタントツール',
|
||||
com_nav_tool_dialog_description:
|
||||
'ツールの選択を維持するには、アシスタントを保存する必要があります。',
|
||||
|
|
@ -2165,7 +2165,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'アンインストール',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: '追加',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ export default {
|
|||
com_nav_change_picture: '프로필 사진 변경',
|
||||
com_nav_plugin_install: '플러그인 설치',
|
||||
com_nav_plugin_uninstall: '플러그인 제거',
|
||||
com_nav_tool_add: '추가',
|
||||
com_ui_add: '추가',
|
||||
com_nav_tool_remove: '제거',
|
||||
com_nav_tool_dialog: '어시스턴트 도구',
|
||||
com_nav_tool_dialog_description: 'Assistant를 저장해야 도구 선택이 유지됩니다.',
|
||||
|
|
@ -2595,7 +2595,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: '플러그인 제거',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: '추가',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -567,7 +567,7 @@ export default {
|
|||
com_nav_welcome_assistant: 'Выберите ассистента',
|
||||
com_nav_plugin_install: 'Установить',
|
||||
com_nav_plugin_uninstall: 'Удалить',
|
||||
com_nav_tool_add: 'Добавить',
|
||||
com_ui_add: 'Добавить',
|
||||
com_nav_tool_remove: 'Удалить',
|
||||
com_nav_tool_dialog: 'Инструменты ассистента',
|
||||
com_nav_tool_dialog_description:
|
||||
|
|
@ -2656,7 +2656,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: 'Удалить',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: 'Добавить',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ export default {
|
|||
com_nav_plugin_store: 'Eklenti mağazası',
|
||||
com_nav_plugin_install: 'Yükle',
|
||||
com_nav_plugin_uninstall: 'Kaldır',
|
||||
com_nav_tool_add: 'Ekle',
|
||||
com_ui_add: 'Ekle',
|
||||
com_nav_tool_remove: 'Kaldır',
|
||||
com_nav_tool_dialog: 'Asistan Araçları',
|
||||
com_nav_tool_dialog_description: 'Araç seçimlerinin kalıcı olması için asistan kaydedilmelidir.',
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ export default {
|
|||
com_nav_plugin_store: '插件商店',
|
||||
com_nav_plugin_install: '安装',
|
||||
com_nav_plugin_uninstall: '卸载',
|
||||
com_nav_tool_add: '添加',
|
||||
com_ui_add: '添加',
|
||||
com_nav_tool_remove: '移除',
|
||||
com_nav_tool_dialog: '助手工具',
|
||||
com_nav_tool_dialog_description: '必须保存助手才能保留工具选择。',
|
||||
|
|
@ -2071,7 +2071,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: '卸载',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: '添加',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -522,7 +522,7 @@ export default {
|
|||
com_nav_change_picture: '更換圖片',
|
||||
com_nav_plugin_install: '安裝',
|
||||
com_nav_plugin_uninstall: '解除安裝',
|
||||
com_nav_tool_add: '新增',
|
||||
com_ui_add: '新增',
|
||||
com_nav_tool_remove: '移除',
|
||||
com_nav_tool_dialog: 'AI 工具',
|
||||
com_nav_tool_dialog_description: '必須儲存 Assistant 才能保留工具選擇。',
|
||||
|
|
@ -2563,7 +2563,7 @@ export const comparisons = {
|
|||
english: 'Uninstall',
|
||||
translated: '解除安裝',
|
||||
},
|
||||
com_nav_tool_add: {
|
||||
com_ui_add: {
|
||||
english: 'Add',
|
||||
translated: '新增',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1355,7 +1355,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: Desinstalar
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: Adicionar
|
||||
|
||||
|
|
|
|||
|
|
@ -1379,7 +1379,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: Deinstallieren
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: Hinzufügen
|
||||
|
||||
|
|
|
|||
|
|
@ -1355,7 +1355,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: Desinstalar
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: Agregar
|
||||
|
||||
|
|
|
|||
|
|
@ -1155,7 +1155,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: הסר התקנה
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: הוסף
|
||||
|
||||
|
|
|
|||
|
|
@ -1507,7 +1507,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: Disinstalla
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: Aggiungi
|
||||
|
||||
|
|
|
|||
|
|
@ -1407,7 +1407,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: アンインストール
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: 追加
|
||||
|
||||
|
|
|
|||
|
|
@ -1359,7 +1359,7 @@ Write a prompt that is mindful of the nuances in the language with respect to it
|
|||
- **english**: Uninstall
|
||||
- **translated**: 卸载
|
||||
|
||||
- **com_nav_tool_add**:
|
||||
- **com_ui_add**:
|
||||
- **english**: Add
|
||||
- **translated**: 添加
|
||||
|
||||
|
|
|
|||
|
|
@ -328,3 +328,13 @@
|
|||
.sp-wrapper {
|
||||
@apply flex h-full w-full grow flex-col justify-center;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
|
||||
20%, 40%, 60%, 80% { transform: translateX(3px); }
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 0.5s cubic-bezier(.36,.07,.19,.97) both;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1172,7 +1172,7 @@ button {
|
|||
.btn-neutral {
|
||||
--tw-bg-opacity: 1;
|
||||
--tw-text-opacity: 1;
|
||||
background-color: rgba(255, 255, 255, var(--tw-bg-opacity));
|
||||
background-color: var(--surface-secondary);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
border-width: 1px;
|
||||
color: rgba(64, 65, 79, var(--tw-text-opacity));
|
||||
|
|
|
|||
|
|
@ -60,13 +60,13 @@ export const cardStyle =
|
|||
'transition-colors rounded-md min-w-[75px] border font-normal bg-white hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-700 dark:bg-gray-800 text-black dark:text-gray-600 focus:outline-none data-[state=open]:bg-gray-50 dark:data-[state=open]:bg-gray-700';
|
||||
|
||||
export const defaultTextProps =
|
||||
'rounded-md border border-gray-200 focus:border-gray-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none focus-within:placeholder:text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:focus:bg-gray-600 dark:focus:border-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:outline-none';
|
||||
'rounded-md border border-gray-200 focus:border-gray-400 focus:bg-gray-50 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.05)] outline-none focus-within:placeholder:text-text-primary focus:placeholder:text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-gray-400 focus:ring-opacity-20 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-700 dark:border-gray-600 dark:focus:bg-gray-600 dark:focus:border-gray-600 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:outline-none';
|
||||
|
||||
export const optionText =
|
||||
'p-0 shadow-none text-right pr-1 h-8 border-transparent hover:bg-gray-800/10 dark:hover:bg-white/10 dark:focus:bg-white/10 transition-colors';
|
||||
|
||||
export const defaultTextPropsLabel =
|
||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none focus-within:placeholder:text-text-primary placeholder:text-text-secondary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-600 dark:focus:outline-none';
|
||||
'rounded-md border border-gray-300 bg-transparent text-sm shadow-[0_0_10px_rgba(0,0,0,0.10)] outline-none focus-within:placeholder:text-text-primary focus:placeholder:text-text-primary placeholder:text-text-secondary focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-50 dark:shadow-[0_0_15px_rgba(0,0,0,0.10)] dark:focus:border-gray-600 dark:focus:outline-none';
|
||||
|
||||
export function capitalizeFirstLetter(string: string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
|
|
|
|||
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -30357,6 +30357,7 @@
|
|||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
|
|
|
|||
|
|
@ -972,6 +972,8 @@ export enum Constants {
|
|||
DEFAULT_STREAM_RATE = 1,
|
||||
/** Saved Tag */
|
||||
SAVED_TAG = 'Saved',
|
||||
/** Max number of Conversation starters for Agents/Assistants */
|
||||
MAX_CONVO_STARTERS = 4,
|
||||
}
|
||||
|
||||
export enum LocalStorageKeys {
|
||||
|
|
|
|||
|
|
@ -255,14 +255,18 @@ export function getAssistantDocs({
|
|||
endpoint,
|
||||
version,
|
||||
}: {
|
||||
endpoint: s.AssistantsEndpoint;
|
||||
endpoint: s.AssistantsEndpoint | string;
|
||||
version: number | string;
|
||||
}): Promise<a.AssistantDocument[]> {
|
||||
if (!s.isAssistantsEndpoint(endpoint)) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return request.get(
|
||||
endpoints.assistants({
|
||||
path: 'documents',
|
||||
version,
|
||||
endpoint,
|
||||
options: { endpoint },
|
||||
endpoint: endpoint as s.AssistantsEndpoint,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ export const defaultAssistantFormValues = {
|
|||
name: '',
|
||||
description: '',
|
||||
instructions: '',
|
||||
conversation_starters: [],
|
||||
model: '',
|
||||
functions: [],
|
||||
code_interpreter: false,
|
||||
|
|
|
|||
|
|
@ -70,13 +70,14 @@ export type Assistant = {
|
|||
id: string;
|
||||
created_at: number;
|
||||
description: string | null;
|
||||
file_ids: string[];
|
||||
file_ids?: string[];
|
||||
instructions: string | null;
|
||||
conversation_starters?: string[];
|
||||
metadata: Metadata | null;
|
||||
model: string;
|
||||
name: string | null;
|
||||
object: string;
|
||||
tools: FunctionTool[];
|
||||
tools?: FunctionTool[];
|
||||
tool_resources?: ToolResources;
|
||||
};
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ export type AssistantCreateParams = {
|
|||
description?: string | null;
|
||||
file_ids?: string[];
|
||||
instructions?: string | null;
|
||||
conversation_starters?: string[];
|
||||
metadata?: Metadata | null;
|
||||
name?: string | null;
|
||||
tools?: Array<FunctionTool | string>;
|
||||
|
|
@ -99,6 +101,7 @@ export type AssistantUpdateParams = {
|
|||
description?: string | null;
|
||||
file_ids?: string[];
|
||||
instructions?: string | null;
|
||||
conversation_starters?: string[] | null;
|
||||
metadata?: Metadata | null;
|
||||
name?: string | null;
|
||||
tools?: Array<FunctionTool | string>;
|
||||
|
|
@ -392,6 +395,7 @@ export type AssistantAvatar = {
|
|||
export type AssistantDocument = {
|
||||
user: string;
|
||||
assistant_id: string;
|
||||
conversation_starters?: string[];
|
||||
avatar?: AssistantAvatar;
|
||||
access_level?: number;
|
||||
file_ids?: string[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue