mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
🔖 feat: Conversation Bookmarks (#3344)
* feat: add tags property in Conversation model * feat: add ConversationTag model * feat: add the tags parameter to getConvosByPage * feat: add API route to ConversationTag * feat: add types of ConversationTag * feat: add data access functions for conversation tags * feat: add Bookmark table component * feat: Add an action to bookmark * feat: add Bookmark nav component * fix: failed test * refactor: made 'Saved' tag a constant * feat: add new bookmark to current conversation * chore: Add comment * fix: delete tag from conversations when it's deleted * fix: Update the query cache when the tag title is changed. * chore: fix typo * refactor: add description of rebuilding bookmarks * chore: remove unused variables * fix: position when adding a new bookmark * refactor: add comment, rename a function * refactor: add a unique constraint in ConversationTag * chore: add localizations
This commit is contained in:
parent
d4d56281e3
commit
e565e0faab
65 changed files with 3751 additions and 36 deletions
|
@ -73,13 +73,16 @@ module.exports = {
|
|||
throw new Error('Failed to save conversations in bulk.');
|
||||
}
|
||||
},
|
||||
getConvosByPage: async (user, pageNumber = 1, pageSize = 25, isArchived = false) => {
|
||||
getConvosByPage: async (user, pageNumber = 1, pageSize = 25, isArchived = false, tags) => {
|
||||
const query = { user };
|
||||
if (isArchived) {
|
||||
query.isArchived = true;
|
||||
} else {
|
||||
query.$or = [{ isArchived: false }, { isArchived: { $exists: false } }];
|
||||
}
|
||||
if (Array.isArray(tags) && tags.length > 0) {
|
||||
query.tags = { $in: tags };
|
||||
}
|
||||
try {
|
||||
const totalConvos = (await Conversation.countDocuments(query)) || 1;
|
||||
const totalPages = Math.ceil(totalConvos / pageSize);
|
||||
|
|
268
api/models/ConversationTag.js
Normal file
268
api/models/ConversationTag.js
Normal file
|
@ -0,0 +1,268 @@
|
|||
//const crypto = require('crypto');
|
||||
|
||||
const logger = require('~/config/winston');
|
||||
const Conversation = require('./schema/convoSchema');
|
||||
const ConversationTag = require('./schema/conversationTagSchema');
|
||||
|
||||
const SAVED_TAG = 'Saved';
|
||||
|
||||
const updateTagsForConversation = async (user, conversationId, tags) => {
|
||||
try {
|
||||
const conversation = await Conversation.findOne({ user, conversationId });
|
||||
if (!conversation) {
|
||||
return { message: 'Conversation not found' };
|
||||
}
|
||||
|
||||
const addedTags = tags.tags.filter((tag) => !conversation.tags.includes(tag));
|
||||
const removedTags = conversation.tags.filter((tag) => !tags.tags.includes(tag));
|
||||
for (const tag of addedTags) {
|
||||
await ConversationTag.updateOne({ tag, user }, { $inc: { count: 1 } }, { upsert: true });
|
||||
}
|
||||
for (const tag of removedTags) {
|
||||
await ConversationTag.updateOne({ tag, user }, { $inc: { count: -1 } });
|
||||
}
|
||||
conversation.tags = tags.tags;
|
||||
await conversation.save({ timestamps: { updatedAt: false } });
|
||||
return conversation.tags;
|
||||
} catch (error) {
|
||||
logger.error('[updateTagsToConversation] Error updating tags', error);
|
||||
return { message: 'Error updating tags' };
|
||||
}
|
||||
};
|
||||
|
||||
const createConversationTag = async (user, data) => {
|
||||
try {
|
||||
const cTag = await ConversationTag.findOne({ user, tag: data.tag });
|
||||
if (cTag) {
|
||||
return cTag;
|
||||
}
|
||||
|
||||
const addToConversation = data.addToConversation && data.conversationId;
|
||||
const newTag = await ConversationTag.create({
|
||||
user,
|
||||
tag: data.tag,
|
||||
count: 0,
|
||||
description: data.description,
|
||||
position: 1,
|
||||
});
|
||||
|
||||
await ConversationTag.updateMany(
|
||||
{ user, position: { $gte: 1 }, _id: { $ne: newTag._id } },
|
||||
{ $inc: { position: 1 } },
|
||||
);
|
||||
|
||||
if (addToConversation) {
|
||||
const conversation = await Conversation.findOne({
|
||||
user,
|
||||
conversationId: data.conversationId,
|
||||
});
|
||||
if (conversation) {
|
||||
const tags = [...(conversation.tags || []), data.tag];
|
||||
await updateTagsForConversation(user, data.conversationId, { tags });
|
||||
} else {
|
||||
logger.warn('[updateTagsForConversation] Conversation not found', data.conversationId);
|
||||
}
|
||||
}
|
||||
|
||||
return await ConversationTag.findOne({ user, tag: data.tag });
|
||||
} catch (error) {
|
||||
logger.error('[createConversationTag] Error updating conversation tag', error);
|
||||
return { message: 'Error updating conversation tag' };
|
||||
}
|
||||
};
|
||||
|
||||
const replaceOrRemoveTagInConversations = async (user, oldtag, newtag) => {
|
||||
try {
|
||||
const conversations = await Conversation.find({ user, tags: { $in: [oldtag] } });
|
||||
for (const conversation of conversations) {
|
||||
if (newtag && newtag !== '') {
|
||||
conversation.tags = conversation.tags.map((tag) => (tag === oldtag ? newtag : tag));
|
||||
} else {
|
||||
conversation.tags = conversation.tags.filter((tag) => tag !== oldtag);
|
||||
}
|
||||
await conversation.save({ timestamps: { updatedAt: false } });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[replaceOrRemoveTagInConversations] Error updating conversation tags', error);
|
||||
return { message: 'Error updating conversation tags' };
|
||||
}
|
||||
};
|
||||
|
||||
const updateTagPosition = async (user, tag, newPosition) => {
|
||||
try {
|
||||
const cTag = await ConversationTag.findOne({ user, tag });
|
||||
if (!cTag) {
|
||||
return { message: 'Tag not found' };
|
||||
}
|
||||
|
||||
const oldPosition = cTag.position;
|
||||
|
||||
if (newPosition === oldPosition) {
|
||||
return cTag;
|
||||
}
|
||||
|
||||
const updateOperations = [];
|
||||
|
||||
if (newPosition > oldPosition) {
|
||||
// Move other tags up
|
||||
updateOperations.push({
|
||||
updateMany: {
|
||||
filter: {
|
||||
user,
|
||||
position: { $gt: oldPosition, $lte: newPosition },
|
||||
tag: { $ne: SAVED_TAG },
|
||||
},
|
||||
update: { $inc: { position: -1 } },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Move other tags down
|
||||
updateOperations.push({
|
||||
updateMany: {
|
||||
filter: {
|
||||
user,
|
||||
position: { $gte: newPosition, $lt: oldPosition },
|
||||
tag: { $ne: SAVED_TAG },
|
||||
},
|
||||
update: { $inc: { position: 1 } },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Update the target tag's position
|
||||
updateOperations.push({
|
||||
updateOne: {
|
||||
filter: { _id: cTag._id },
|
||||
update: { $set: { position: newPosition } },
|
||||
},
|
||||
});
|
||||
|
||||
await ConversationTag.bulkWrite(updateOperations);
|
||||
|
||||
return await ConversationTag.findById(cTag._id);
|
||||
} catch (error) {
|
||||
logger.error('[updateTagPosition] Error updating tag position', error);
|
||||
return { message: 'Error updating tag position' };
|
||||
}
|
||||
};
|
||||
module.exports = {
|
||||
SAVED_TAG,
|
||||
ConversationTag,
|
||||
getConversationTags: async (user) => {
|
||||
try {
|
||||
const cTags = await ConversationTag.find({ user }).sort({ position: 1 }).lean();
|
||||
cTags.sort((a, b) => (a.tag === SAVED_TAG ? -1 : b.tag === SAVED_TAG ? 1 : 0));
|
||||
|
||||
return cTags;
|
||||
} catch (error) {
|
||||
logger.error('[getShare] Error getting share link', error);
|
||||
return { message: 'Error getting share link' };
|
||||
}
|
||||
},
|
||||
|
||||
createConversationTag,
|
||||
updateConversationTag: async (user, tag, data) => {
|
||||
try {
|
||||
const cTag = await ConversationTag.findOne({ user, tag });
|
||||
if (!cTag) {
|
||||
return createConversationTag(user, data);
|
||||
}
|
||||
|
||||
if (cTag.tag !== data.tag || cTag.description !== data.description) {
|
||||
cTag.tag = data.tag;
|
||||
cTag.description = data.description === undefined ? cTag.description : data.description;
|
||||
await cTag.save();
|
||||
}
|
||||
|
||||
if (data.position !== undefined && cTag.position !== data.position) {
|
||||
await updateTagPosition(user, tag, data.position);
|
||||
}
|
||||
|
||||
// update conversation tags properties
|
||||
replaceOrRemoveTagInConversations(user, tag, data.tag);
|
||||
return await ConversationTag.findOne({ user, tag: data.tag });
|
||||
} catch (error) {
|
||||
logger.error('[updateConversationTag] Error updating conversation tag', error);
|
||||
return { message: 'Error updating conversation tag' };
|
||||
}
|
||||
},
|
||||
|
||||
deleteConversationTag: async (user, tag) => {
|
||||
try {
|
||||
const currentTag = await ConversationTag.findOne({ user, tag });
|
||||
if (!currentTag) {
|
||||
return;
|
||||
}
|
||||
|
||||
await currentTag.deleteOne({ user, tag });
|
||||
|
||||
await replaceOrRemoveTagInConversations(user, tag, null);
|
||||
return currentTag;
|
||||
} catch (error) {
|
||||
logger.error('[deleteConversationTag] Error deleting conversation tag', error);
|
||||
return { message: 'Error deleting conversation tag' };
|
||||
}
|
||||
},
|
||||
|
||||
updateTagsForConversation,
|
||||
rebuildConversationTags: async (user) => {
|
||||
try {
|
||||
const conversations = await Conversation.find({ user }).select('tags');
|
||||
const tagCountMap = {};
|
||||
|
||||
// Count the occurrences of each tag
|
||||
conversations.forEach((conversation) => {
|
||||
conversation.tags.forEach((tag) => {
|
||||
if (tagCountMap[tag]) {
|
||||
tagCountMap[tag]++;
|
||||
} else {
|
||||
tagCountMap[tag] = 1;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const tags = await ConversationTag.find({ user }).sort({ position: -1 });
|
||||
|
||||
// Update existing tags and add new tags
|
||||
for (const [tag, count] of Object.entries(tagCountMap)) {
|
||||
const existingTag = tags.find((t) => t.tag === tag);
|
||||
if (existingTag) {
|
||||
existingTag.count = count;
|
||||
await existingTag.save();
|
||||
} else {
|
||||
const newTag = new ConversationTag({ user, tag, count });
|
||||
tags.push(newTag);
|
||||
await newTag.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Set count to 0 for tags that are not in the grouped tags
|
||||
for (const tag of tags) {
|
||||
if (!tagCountMap[tag.tag]) {
|
||||
tag.count = 0;
|
||||
await tag.save();
|
||||
}
|
||||
}
|
||||
|
||||
// Sort tags by position in descending order
|
||||
tags.sort((a, b) => a.position - b.position);
|
||||
|
||||
// Move the tag with name "saved" to the first position
|
||||
const savedTagIndex = tags.findIndex((tag) => tag.tag === SAVED_TAG);
|
||||
if (savedTagIndex !== -1) {
|
||||
const [savedTag] = tags.splice(savedTagIndex, 1);
|
||||
tags.unshift(savedTag);
|
||||
}
|
||||
|
||||
// Reassign positions starting from 0
|
||||
tags.forEach((tag, index) => {
|
||||
tag.position = index;
|
||||
tag.save();
|
||||
});
|
||||
return tags;
|
||||
} catch (error) {
|
||||
logger.error('[rearrangeTags] Error rearranging tags', error);
|
||||
return { message: 'Error rearranging tags' };
|
||||
}
|
||||
},
|
||||
};
|
31
api/models/schema/conversationTagSchema.js
Normal file
31
api/models/schema/conversationTagSchema.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const conversationTagSchema = mongoose.Schema(
|
||||
{
|
||||
tag: {
|
||||
type: String,
|
||||
index: true,
|
||||
},
|
||||
user: {
|
||||
type: String,
|
||||
index: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
index: true,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
position: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
{ timestamps: true },
|
||||
);
|
||||
|
||||
conversationTagSchema.index({ tag: 1, user: 1 }, { unique: true });
|
||||
|
||||
module.exports = mongoose.model('ConversationTag', conversationTagSchema);
|
|
@ -42,6 +42,11 @@ const convoSchema = mongoose.Schema(
|
|||
invocationId: {
|
||||
type: Number,
|
||||
},
|
||||
tags: {
|
||||
type: [String],
|
||||
default: [],
|
||||
meiliIndex: true,
|
||||
},
|
||||
},
|
||||
{ timestamps: true },
|
||||
);
|
||||
|
|
|
@ -103,6 +103,10 @@ const conversationPreset = {
|
|||
spec: {
|
||||
type: String,
|
||||
},
|
||||
tags: {
|
||||
type: [String],
|
||||
default: [],
|
||||
},
|
||||
tools: { type: [{ type: String }], default: undefined },
|
||||
maxContextTokens: {
|
||||
type: Number,
|
||||
|
|
|
@ -94,6 +94,7 @@ const startServer = async () => {
|
|||
app.use('/api/share', routes.share);
|
||||
app.use('/api/roles', routes.roles);
|
||||
|
||||
app.use('/api/tags', routes.tags);
|
||||
app.use((req, res) => {
|
||||
res.sendFile(path.join(app.locals.paths.dist, 'index.html'));
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
|
|||
const { forkConversation } = require('~/server/utils/import/fork');
|
||||
const { importConversations } = require('~/server/utils/import');
|
||||
const { createImportLimiters } = require('~/server/middleware');
|
||||
const { updateTagsForConversation } = require('~/models/ConversationTag');
|
||||
const getLogStores = require('~/cache/getLogStores');
|
||||
const { sleep } = require('~/server/utils');
|
||||
const { logger } = require('~/config');
|
||||
|
@ -30,8 +31,13 @@ router.get('/', async (req, res) => {
|
|||
return res.status(400).json({ error: 'Invalid page size' });
|
||||
}
|
||||
const isArchived = req.query.isArchived === 'true';
|
||||
const tags = req.query.tags
|
||||
? Array.isArray(req.query.tags)
|
||||
? req.query.tags
|
||||
: [req.query.tags]
|
||||
: undefined;
|
||||
|
||||
res.status(200).send(await getConvosByPage(req.user.id, pageNumber, pageSize, isArchived));
|
||||
res.status(200).send(await getConvosByPage(req.user.id, pageNumber, pageSize, isArchived, tags));
|
||||
});
|
||||
|
||||
router.get('/:conversationId', async (req, res) => {
|
||||
|
@ -167,4 +173,9 @@ router.post('/fork', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.put('/tags/:conversationId', async (req, res) => {
|
||||
const tag = await updateTagsForConversation(req.user.id, req.params.conversationId, req.body);
|
||||
res.status(200).json(tag);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -21,6 +21,7 @@ const staticRoute = require('./static');
|
|||
const share = require('./share');
|
||||
const categories = require('./categories');
|
||||
const roles = require('./roles');
|
||||
const tags = require('./tags');
|
||||
|
||||
module.exports = {
|
||||
search,
|
||||
|
@ -46,4 +47,5 @@ module.exports = {
|
|||
share,
|
||||
categories,
|
||||
roles,
|
||||
tags,
|
||||
};
|
||||
|
|
44
api/server/routes/tags.js
Normal file
44
api/server/routes/tags.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
const express = require('express');
|
||||
|
||||
const {
|
||||
getConversationTags,
|
||||
updateConversationTag,
|
||||
createConversationTag,
|
||||
deleteConversationTag,
|
||||
rebuildConversationTags,
|
||||
} = require('~/models/ConversationTag');
|
||||
const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
|
||||
const router = express.Router();
|
||||
router.use(requireJwtAuth);
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const tags = await getConversationTags(req.user.id);
|
||||
|
||||
if (tags) {
|
||||
res.status(200).json(tags);
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const tag = await createConversationTag(req.user.id, req.body);
|
||||
res.status(200).json(tag);
|
||||
});
|
||||
|
||||
router.post('/rebuild', async (req, res) => {
|
||||
const tag = await rebuildConversationTags(req.user.id);
|
||||
res.status(200).json(tag);
|
||||
});
|
||||
|
||||
router.put('/:tag', async (req, res) => {
|
||||
const tag = await updateConversationTag(req.user.id, req.params.tag, req.body);
|
||||
res.status(200).json(tag);
|
||||
});
|
||||
|
||||
router.delete('/:tag', async (req, res) => {
|
||||
const tag = await deleteConversationTag(req.user.id, req.params.tag);
|
||||
res.status(200).json(tag);
|
||||
});
|
||||
|
||||
module.exports = router;
|
9
client/src/Providers/BookmarkContext.tsx
Normal file
9
client/src/Providers/BookmarkContext.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
import type { TConversationTag } from 'librechat-data-provider';
|
||||
|
||||
type TBookmarkContext = { bookmarks: TConversationTag[] };
|
||||
|
||||
export const BookmarkContext = createContext<TBookmarkContext>({
|
||||
bookmarks: [],
|
||||
} as TBookmarkContext);
|
||||
export const useBookmarkContext = () => useContext(BookmarkContext);
|
|
@ -7,6 +7,7 @@ export * from './SearchContext';
|
|||
export * from './FileMapContext';
|
||||
export * from './AddedChatContext';
|
||||
export * from './ChatFormContext';
|
||||
export * from './BookmarkContext';
|
||||
export * from './DashboardContext';
|
||||
export * from './AssistantsContext';
|
||||
export * from './AssistantsMapContext';
|
||||
|
|
|
@ -83,7 +83,7 @@ export type NavLink = {
|
|||
label?: string;
|
||||
icon: LucideIcon | React.FC;
|
||||
Component?: React.ComponentType;
|
||||
onClick?: () => void;
|
||||
onClick?: (e?: React.MouseEvent) => void;
|
||||
variant?: 'default' | 'ghost';
|
||||
id: string;
|
||||
};
|
||||
|
|
69
client/src/components/Bookmarks/BookmarkEditDialog.tsx
Normal file
69
client/src/components/Bookmarks/BookmarkEditDialog.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import { DialogTrigger } from '@radix-ui/react-dialog';
|
||||
import { TConversationTag, TConversation } from 'librechat-data-provider';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { Dialog, DialogButton } from '~/components/ui/';
|
||||
import BookmarkForm from './BookmarkForm';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { Spinner } from '../svg';
|
||||
|
||||
type BookmarkEditDialogProps = {
|
||||
bookmark?: TConversationTag;
|
||||
conversation?: TConversation;
|
||||
tags?: string[];
|
||||
setTags?: (tags: string[]) => void;
|
||||
trigger: React.ReactNode;
|
||||
};
|
||||
const BookmarkEditDialog = ({
|
||||
bookmark,
|
||||
conversation,
|
||||
tags,
|
||||
setTags,
|
||||
trigger,
|
||||
}: BookmarkEditDialogProps) => {
|
||||
const localize = useLocalize();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const handleSubmitForm = () => {
|
||||
if (formRef.current) {
|
||||
formRef.current.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||
<DialogTemplate
|
||||
title="Bookmark"
|
||||
className="w-11/12 sm:w-1/4"
|
||||
showCloseButton={false}
|
||||
main={
|
||||
<BookmarkForm
|
||||
conversation={conversation}
|
||||
onOpenChange={setOpen}
|
||||
setIsLoading={setIsLoading}
|
||||
bookmark={bookmark}
|
||||
formRef={formRef}
|
||||
setTags={setTags}
|
||||
tags={tags}
|
||||
/>
|
||||
}
|
||||
buttons={
|
||||
<div className="mb-6 md:mb-2">
|
||||
<DialogButton
|
||||
disabled={isLoading}
|
||||
onClick={handleSubmitForm}
|
||||
className="bg-green-500 text-white hover:bg-green-600 dark:hover:bg-green-600"
|
||||
>
|
||||
{isLoading ? <Spinner /> : localize('com_ui_save')}
|
||||
</DialogButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkEditDialog;
|
195
client/src/components/Bookmarks/BookmarkForm.tsx
Normal file
195
client/src/components/Bookmarks/BookmarkForm.tsx
Normal file
|
@ -0,0 +1,195 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import type {
|
||||
TConversationTag,
|
||||
TConversation,
|
||||
TConversationTagRequest,
|
||||
} from 'librechat-data-provider';
|
||||
import { cn, removeFocusOutlines, defaultTextProps } from '~/utils/';
|
||||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { useConversationTagMutation } from '~/data-provider';
|
||||
import { Checkbox, Label, TextareaAutosize } from '~/components/ui/';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
type TBookmarkFormProps = {
|
||||
bookmark?: TConversationTag;
|
||||
conversation?: TConversation;
|
||||
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
formRef: React.RefObject<HTMLFormElement>;
|
||||
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
tags?: string[];
|
||||
setTags?: (tags: string[]) => void;
|
||||
};
|
||||
const BookmarkForm = ({
|
||||
bookmark,
|
||||
conversation,
|
||||
onOpenChange,
|
||||
formRef,
|
||||
setIsLoading,
|
||||
tags,
|
||||
setTags,
|
||||
}: TBookmarkFormProps) => {
|
||||
const { showToast } = useToastContext();
|
||||
const localize = useLocalize();
|
||||
const mutation = useConversationTagMutation(bookmark?.tag);
|
||||
const { bookmarks } = useBookmarkContext();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
getValues,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm<TConversationTagRequest>({
|
||||
defaultValues: {
|
||||
tag: bookmark?.tag || '',
|
||||
description: bookmark?.description || '',
|
||||
conversationId: conversation?.conversationId || '',
|
||||
addToConversation: conversation ? true : false,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (bookmark) {
|
||||
setValue('tag', bookmark.tag || '');
|
||||
setValue('description', bookmark.description || '');
|
||||
}
|
||||
}, [bookmark, setValue]);
|
||||
|
||||
const onSubmit = (data: TConversationTagRequest) => {
|
||||
if (mutation.isLoading) {
|
||||
return;
|
||||
}
|
||||
if (data.tag === bookmark?.tag && data.description === bookmark?.description) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
mutation.mutate(data, {
|
||||
onSuccess: () => {
|
||||
showToast({
|
||||
message: bookmark
|
||||
? localize('com_ui_bookmarks_update_success')
|
||||
: localize('com_ui_bookmarks_create_success'),
|
||||
});
|
||||
setIsLoading(false);
|
||||
onOpenChange(false);
|
||||
if (setTags && data.addToConversation) {
|
||||
const newTags = [...(tags || []), data.tag].filter(
|
||||
(tag) => tag !== undefined,
|
||||
) as string[];
|
||||
setTags(newTags);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: bookmark
|
||||
? localize('com_ui_bookmarks_update_error')
|
||||
: localize('com_ui_bookmarks_create_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
setIsLoading(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
ref={formRef}
|
||||
className="mt-6"
|
||||
aria-label="Bookmark form"
|
||||
method="POST"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="bookmark-tag" className="text-left text-sm font-medium">
|
||||
{localize('com_ui_bookmarks_title')}
|
||||
</Label>
|
||||
<input
|
||||
type="text"
|
||||
id="bookmark-tag"
|
||||
aria-label="Bookmark"
|
||||
{...register('tag', {
|
||||
required: 'tag is required',
|
||||
maxLength: {
|
||||
value: 128,
|
||||
message: localize('com_auth_password_max_length'),
|
||||
},
|
||||
validate: (value) => {
|
||||
return (
|
||||
value === bookmark?.tag ||
|
||||
bookmarks.every((bookmark) => bookmark.tag !== value) ||
|
||||
'tag must be unique'
|
||||
);
|
||||
},
|
||||
})}
|
||||
aria-invalid={!!errors.tag}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex h-10 max-h-10 w-full resize-none border-gray-100 px-3 py-2 dark:border-gray-600',
|
||||
removeFocusOutlines,
|
||||
)}
|
||||
placeholder=" "
|
||||
/>
|
||||
{errors.tag && <span className="text-sm text-red-500">{errors.tag.message}</span>}
|
||||
</div>
|
||||
|
||||
<div className="grid w-full items-center gap-2">
|
||||
<Label htmlFor="bookmark-description" className="text-left text-sm font-medium">
|
||||
{localize('com_ui_bookmarks_description')}
|
||||
</Label>
|
||||
<TextareaAutosize
|
||||
{...register('description', {
|
||||
maxLength: {
|
||||
value: 1048,
|
||||
message: 'Maximum 1048 characters',
|
||||
},
|
||||
})}
|
||||
id="bookmark-description"
|
||||
disabled={false}
|
||||
className={cn(
|
||||
defaultTextProps,
|
||||
'flex max-h-[138px] min-h-[100px] w-full resize-none px-3 py-2',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{conversation && (
|
||||
<div className="flex w-full items-center">
|
||||
<Controller
|
||||
name="addToConversation"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
className="relative float-left mr-2 inline-flex h-4 w-4 cursor-pointer"
|
||||
value={field?.value?.toString()}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
className="form-check-label text-token-text-primary w-full cursor-pointer"
|
||||
htmlFor="addToConversation"
|
||||
onClick={() =>
|
||||
setValue('addToConversation', !getValues('addToConversation'), {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex select-none items-center">
|
||||
{localize('com_ui_bookmarks_add_to_conversation')}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkForm;
|
74
client/src/components/Bookmarks/BookmarkItem.tsx
Normal file
74
client/src/components/Bookmarks/BookmarkItem.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { useState } from 'react';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import type { FC } from 'react';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type MenuItemProps = {
|
||||
tag: string;
|
||||
selected: boolean;
|
||||
count?: number;
|
||||
handleSubmit: (tag: string) => Promise<void>;
|
||||
icon?: React.ReactNode;
|
||||
highlightSelected?: boolean;
|
||||
};
|
||||
|
||||
const BookmarkItem: FC<MenuItemProps> = ({
|
||||
tag,
|
||||
selected,
|
||||
count,
|
||||
handleSubmit,
|
||||
icon,
|
||||
highlightSelected,
|
||||
...rest
|
||||
}) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const clickHandler = async () => {
|
||||
setIsLoading(true);
|
||||
await handleSubmit(tag);
|
||||
setIsLoading(false);
|
||||
};
|
||||
return (
|
||||
<div
|
||||
role="menuitem"
|
||||
className={cn(
|
||||
'group m-1.5 flex cursor-pointer gap-2 rounded px-1 py-2.5 !pr-3 text-sm !opacity-100 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50',
|
||||
'hover:bg-black/5 dark:hover:bg-white/5',
|
||||
highlightSelected && selected && 'bg-black/5 dark:bg-white/5',
|
||||
)}
|
||||
tabIndex={-1}
|
||||
{...rest}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<div className="flex grow items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{icon ? (
|
||||
icon
|
||||
) : isLoading ? (
|
||||
<Spinner className="size-4" />
|
||||
) : selected ? (
|
||||
<BookmarkFilledIcon className="size-4" />
|
||||
) : (
|
||||
<BookmarkIcon className="size-4" />
|
||||
)}
|
||||
<div className="break-all">{tag}</div>
|
||||
</div>
|
||||
|
||||
{count !== undefined && (
|
||||
<div className="flex items-center justify-end">
|
||||
<span
|
||||
className={cn(
|
||||
'ml-auto w-9 min-w-max whitespace-nowrap rounded-full bg-white px-2.5 py-0.5 text-center text-xs font-medium leading-5 text-gray-600 ring-1 ring-inset ring-gray-200',
|
||||
'dark:bg-gray-800 dark:text-white dark:ring-gray-100/50',
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default BookmarkItem;
|
30
client/src/components/Bookmarks/BookmarkItems.tsx
Normal file
30
client/src/components/Bookmarks/BookmarkItems.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import type { FC } from 'react';
|
||||
import { useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import BookmarkItem from './BookmarkItem';
|
||||
|
||||
const BookmarkItems: FC<{
|
||||
tags: string[];
|
||||
handleSubmit: (tag: string) => Promise<void>;
|
||||
header: React.ReactNode;
|
||||
highlightSelected?: boolean;
|
||||
}> = ({ tags, handleSubmit, header, highlightSelected }) => {
|
||||
const { bookmarks } = useBookmarkContext();
|
||||
return (
|
||||
<>
|
||||
{header}
|
||||
<div className="my-1.5 h-px bg-black/10 dark:bg-white/10" role="none" />
|
||||
{bookmarks.length > 0 &&
|
||||
bookmarks.map((bookmark) => (
|
||||
<BookmarkItem
|
||||
key={bookmark.tag}
|
||||
tag={bookmark.tag}
|
||||
selected={tags.includes(bookmark.tag)}
|
||||
count={bookmark.count}
|
||||
handleSubmit={handleSubmit}
|
||||
highlightSelected={highlightSelected}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default BookmarkItems;
|
49
client/src/components/Bookmarks/DeleteBookmarkButton.tsx
Normal file
49
client/src/components/Bookmarks/DeleteBookmarkButton.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useCallback } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useDeleteConversationTagMutation } from '~/data-provider';
|
||||
import TooltipIcon from '~/components/ui/TooltipIcon';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { TrashIcon } from '~/components/svg';
|
||||
import { Label } from '~/components/ui';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
const DeleteBookmarkButton: FC<{ bookmark: string }> = ({ bookmark }) => {
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
||||
const deleteBookmarkMutation = useDeleteConversationTagMutation({
|
||||
onSuccess: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_bookmarks_delete_success'),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_ui_bookmarks_delete_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const confirmDelete = useCallback(async () => {
|
||||
await deleteBookmarkMutation.mutateAsync(bookmark);
|
||||
}, [bookmark, deleteBookmarkMutation]);
|
||||
|
||||
return (
|
||||
<TooltipIcon
|
||||
disabled={false}
|
||||
appendLabel={false}
|
||||
title="Delete Bookmark"
|
||||
confirmMessage={
|
||||
<Label htmlFor="bookmark" className="text-left text-sm font-medium">
|
||||
{localize('com_ui_bookmark_delete_confirm')} : {bookmark}
|
||||
</Label>
|
||||
}
|
||||
confirm={confirmDelete}
|
||||
className="hover:text-gray-300 focus-visible:bg-gray-100 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
|
||||
icon={<TrashIcon className="size-4" />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default DeleteBookmarkButton;
|
33
client/src/components/Bookmarks/EditBookmarkButton.tsx
Normal file
33
client/src/components/Bookmarks/EditBookmarkButton.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import type { FC } from 'react';
|
||||
import type { TConversationTag } from 'librechat-data-provider';
|
||||
import BookmarkEditDialog from './BookmarkEditDialog';
|
||||
import { EditIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui';
|
||||
const EditBookmarkButton: FC<{ bookmark: TConversationTag }> = ({ bookmark }) => {
|
||||
const localize = useLocalize();
|
||||
return (
|
||||
<BookmarkEditDialog
|
||||
bookmark={bookmark}
|
||||
trigger={
|
||||
<button className="size-4 hover:text-gray-300 focus-visible:bg-gray-100 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600">
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>
|
||||
<EditIcon />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_edit')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditBookmarkButton;
|
6
client/src/components/Bookmarks/index.ts
Normal file
6
client/src/components/Bookmarks/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export { default as DeleteBookmarkButton } from './DeleteBookmarkButton';
|
||||
export { default as EditBookmarkButton } from './EditBookmarkButton';
|
||||
export { default as BookmarkEditDialog } from './BookmarkEditDialog';
|
||||
export { default as BookmarkItems } from './BookmarkItems';
|
||||
export { default as BookmarkItem } from './BookmarkItem';
|
||||
export { default as BookmarkForm } from './BookmarkForm';
|
|
@ -6,6 +6,7 @@ import type { ContextType } from '~/common';
|
|||
import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus';
|
||||
import ExportAndShareMenu from './ExportAndShareMenu';
|
||||
import HeaderOptions from './Input/HeaderOptions';
|
||||
import BookmarkMenu from './Menus/BookmarkMenu';
|
||||
import AddMultiConvo from './AddMultiConvo';
|
||||
import { useMediaQuery } from '~/hooks';
|
||||
|
||||
|
@ -37,6 +38,7 @@ export default function Header() {
|
|||
className="pl-0"
|
||||
/>
|
||||
)}
|
||||
<BookmarkMenu />
|
||||
<AddMultiConvo />
|
||||
</div>
|
||||
{!isSmallScreen && (
|
||||
|
|
134
client/src/components/Chat/Menus/BookmarkMenu.tsx
Normal file
134
client/src/components/Chat/Menus/BookmarkMenu.tsx
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { useEffect, useState, type FC } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TConversation } from 'librechat-data-provider';
|
||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import { useConversationTagsQuery, useTagConversationMutation } from '~/data-provider';
|
||||
import { BookmarkMenuItems } from './Bookmarks/BookmarkMenuItems';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { Spinner } from '~/components';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const SAVED_TAG = 'Saved';
|
||||
const BookmarkMenu: FC = () => {
|
||||
const localize = useLocalize();
|
||||
const location = useLocation();
|
||||
|
||||
const activeConvo = useRecoilValue(store.conversationByIndex(0));
|
||||
|
||||
const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation);
|
||||
const [tags, setTags] = useState<string[]>();
|
||||
|
||||
const [open, setIsOpen] = useState(false);
|
||||
const [conversation, setConversation] = useState<TConversation>();
|
||||
|
||||
let thisConversation: TConversation | null | undefined;
|
||||
if (location.state?.from?.pathname.includes('/chat')) {
|
||||
thisConversation = globalConvo;
|
||||
} else {
|
||||
thisConversation = activeConvo;
|
||||
}
|
||||
|
||||
const { mutateAsync, isLoading } = useTagConversationMutation(
|
||||
thisConversation?.conversationId ?? '',
|
||||
);
|
||||
|
||||
const { data } = useConversationTagsQuery();
|
||||
useEffect(() => {
|
||||
if (
|
||||
(!conversation && thisConversation) ||
|
||||
(conversation &&
|
||||
thisConversation &&
|
||||
conversation.conversationId !== thisConversation.conversationId)
|
||||
) {
|
||||
setConversation(thisConversation);
|
||||
setTags(thisConversation.tags ?? []);
|
||||
}
|
||||
if (tags === undefined && conversation) {
|
||||
setTags(conversation.tags ?? []);
|
||||
}
|
||||
}, [thisConversation, conversation, tags]);
|
||||
|
||||
const isActiveConvo =
|
||||
thisConversation &&
|
||||
thisConversation.conversationId &&
|
||||
thisConversation.conversationId !== 'new' &&
|
||||
thisConversation.conversationId !== 'search';
|
||||
|
||||
if (!isActiveConvo) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const onOpenChange = async (open: boolean) => {
|
||||
if (!open) {
|
||||
setIsOpen(open);
|
||||
return;
|
||||
}
|
||||
if (open && tags && tags.length > 0) {
|
||||
setIsOpen(open);
|
||||
} else {
|
||||
if (thisConversation && thisConversation.conversationId) {
|
||||
await mutateAsync({
|
||||
conversationId: thisConversation.conversationId,
|
||||
tags: [SAVED_TAG],
|
||||
});
|
||||
setTags([SAVED_TAG]);
|
||||
setConversation({ ...thisConversation, tags: [SAVED_TAG] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Root open={open} onOpenChange={onOpenChange}>
|
||||
<Trigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'pointer-cursor relative flex flex-col rounded-md border border-gray-100 bg-white text-left focus:outline-none focus:ring-0 focus:ring-offset-0 dark:border-gray-700 dark:bg-gray-800 sm:text-sm',
|
||||
'hover:bg-gray-50 radix-state-open:bg-gray-50 dark:hover:bg-gray-700 dark:radix-state-open:bg-gray-700',
|
||||
'z-50 flex h-[40px] min-w-4 flex-none items-center justify-center px-3 focus:ring-0 focus:ring-offset-0',
|
||||
)}
|
||||
title={localize('com_ui_bookmarks')}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : tags && tags.length > 0 ? (
|
||||
<BookmarkFilledIcon className="icon-sm" />
|
||||
) : (
|
||||
<BookmarkIcon className="icon-sm" />
|
||||
)}
|
||||
</button>
|
||||
</Trigger>
|
||||
<Portal>
|
||||
<Content
|
||||
className={cn(
|
||||
'grid w-full',
|
||||
'mt-2 min-w-[240px] overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white',
|
||||
'max-h-[500px]',
|
||||
)}
|
||||
side="bottom"
|
||||
align="start"
|
||||
>
|
||||
{data && conversation && (
|
||||
// Display all bookmarks registered by the user and highlight the tags of the currently selected conversation
|
||||
<BookmarkContext.Provider value={{ bookmarks: data }}>
|
||||
<BookmarkMenuItems
|
||||
// Currently selected conversation
|
||||
conversation={conversation}
|
||||
setConversation={setConversation}
|
||||
// Tags in the conversation
|
||||
tags={tags ?? []}
|
||||
// Update tags in the conversation
|
||||
setTags={setTags}
|
||||
/>
|
||||
</BookmarkContext.Provider>
|
||||
)}
|
||||
</Content>
|
||||
</Portal>
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkMenu;
|
|
@ -0,0 +1,77 @@
|
|||
import { useCallback } from 'react';
|
||||
import { BookmarkPlusIcon } from 'lucide-react';
|
||||
import type { FC } from 'react';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { BookmarkItems, BookmarkEditDialog } from '~/components/Bookmarks';
|
||||
import { useTagConversationMutation } from '~/data-provider';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export const BookmarkMenuItems: FC<{
|
||||
conversation: TConversation;
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
setConversation: (conversation: TConversation) => void;
|
||||
}> = ({ conversation, tags, setTags, setConversation }) => {
|
||||
const { showToast } = useToastContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
const { mutateAsync } = useTagConversationMutation(conversation?.conversationId ?? '');
|
||||
const handleSubmit = useCallback(
|
||||
async (tag: string): Promise<void> => {
|
||||
if (tags !== undefined && conversation?.conversationId) {
|
||||
const newTags = tags.includes(tag) ? tags.filter((t) => t !== tag) : [...tags, tag];
|
||||
await mutateAsync(
|
||||
{
|
||||
conversationId: conversation.conversationId,
|
||||
tags: newTags,
|
||||
},
|
||||
{
|
||||
onSuccess: (newTags: string[]) => {
|
||||
setTags(newTags);
|
||||
setConversation({ ...conversation, tags: newTags });
|
||||
},
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: 'Error adding bookmark',
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
[tags, conversation],
|
||||
);
|
||||
|
||||
return (
|
||||
<BookmarkItems
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
header={
|
||||
<div>
|
||||
<BookmarkEditDialog
|
||||
conversation={conversation}
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
trigger={
|
||||
<div
|
||||
role="menuitem"
|
||||
className="group m-1.5 flex cursor-pointer gap-2 rounded px-1 !pr-3.5 pb-2.5 pt-3 text-sm !opacity-100 hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<div className="flex grow items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookmarkPlusIcon className="size-4" />
|
||||
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
99
client/src/components/Nav/Bookmarks/BookmarkNav.tsx
Normal file
99
client/src/components/Nav/Bookmarks/BookmarkNav.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { useState, type FC } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TConversation } from 'librechat-data-provider';
|
||||
import { Content, Portal, Root, Trigger } from '@radix-ui/react-popover';
|
||||
import { BookmarkFilledIcon, BookmarkIcon } from '@radix-ui/react-icons';
|
||||
import { useGetConversationTags } from 'librechat-data-provider/react-query';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import BookmarkNavItems from './BookmarkNavItems';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type BookmarkNavProps = {
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
};
|
||||
const BookmarkNav: FC<BookmarkNavProps> = ({ tags, setTags }: BookmarkNavProps) => {
|
||||
const localize = useLocalize();
|
||||
const location = useLocation();
|
||||
|
||||
const { data } = useGetConversationTags();
|
||||
|
||||
const activeConvo = useRecoilValue(store.conversationByIndex(0));
|
||||
const globalConvo = useRecoilValue(store.conversation) ?? ({} as TConversation);
|
||||
|
||||
const [open, setIsOpen] = useState(false);
|
||||
|
||||
let conversation: TConversation | null | undefined;
|
||||
if (location.state?.from?.pathname.includes('/chat')) {
|
||||
conversation = globalConvo;
|
||||
} else {
|
||||
conversation = activeConvo;
|
||||
}
|
||||
|
||||
// Hide the button if there are no tags
|
||||
if (!data || !data.some((tag) => tag.count > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Root open={open} onOpenChange={setIsOpen}>
|
||||
<Trigger asChild>
|
||||
<button
|
||||
className={cn(
|
||||
'group-ui-open:bg-gray-100 dark:group-ui-open:bg-gray-700 duration-350 mt-text-sm flex h-auto w-full items-center gap-2 rounded-lg p-2 text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-800',
|
||||
open ? 'bg-gray-100 dark:bg-gray-800' : '',
|
||||
)}
|
||||
id="presets-button"
|
||||
data-testid="presets-button"
|
||||
title={localize('com_endpoint_examples')}
|
||||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
<div className="relative flex h-8 w-8 items-center justify-center rounded-full p-1 dark:text-white">
|
||||
{tags.length > 0 ? (
|
||||
<BookmarkFilledIcon className="h-6 w-6" />
|
||||
) : (
|
||||
<BookmarkIcon className="h-6 w-6" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="mt-2 grow overflow-hidden text-ellipsis whitespace-nowrap text-left text-black dark:text-gray-100"
|
||||
style={{ marginTop: '0', marginLeft: '0' }}
|
||||
>
|
||||
{tags.length > 0 ? tags.join(',') : localize('com_ui_bookmarks')}
|
||||
</div>
|
||||
</button>
|
||||
</Trigger>
|
||||
<Portal>
|
||||
<div className="fixed left-0 top-0 z-auto translate-x-[268px] translate-y-[50px]">
|
||||
<Content
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="mt-2 max-h-96 min-w-[240px] overflow-y-auto rounded-lg border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-700 dark:text-white lg:max-h-96"
|
||||
>
|
||||
{data && conversation && data.some((tag) => tag.count > 0) && (
|
||||
// Display bookmarks and highlight the selected tag
|
||||
<BookmarkContext.Provider value={{ bookmarks: data.filter((tag) => tag.count > 0) }}>
|
||||
<BookmarkNavItems
|
||||
// Currently selected conversation
|
||||
conversation={conversation}
|
||||
// List of selected tags(string)
|
||||
tags={tags}
|
||||
// When a user selects a tag, this `setTags` function is called to refetch the list of conversations for the selected tag
|
||||
setTags={setTags}
|
||||
/>
|
||||
</BookmarkContext.Provider>
|
||||
)}
|
||||
</Content>
|
||||
</div>
|
||||
</Portal>
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkNav;
|
58
client/src/components/Nav/Bookmarks/BookmarkNavItems.tsx
Normal file
58
client/src/components/Nav/Bookmarks/BookmarkNavItems.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { useEffect, useState, type FC } from 'react';
|
||||
import { CrossCircledIcon } from '@radix-ui/react-icons';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { BookmarkItems, BookmarkItem } from '~/components/Bookmarks';
|
||||
|
||||
const BookmarkNavItems: FC<{
|
||||
conversation: TConversation;
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
}> = ({ conversation, tags, setTags }) => {
|
||||
const [currentConversation, setCurrentConversation] = useState<TConversation>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentConversation) {
|
||||
setCurrentConversation(conversation);
|
||||
}
|
||||
}, [conversation, currentConversation]);
|
||||
|
||||
const getUpdatedSelected = (tag: string) => {
|
||||
if (tags.some((selectedTag) => selectedTag === tag)) {
|
||||
return tags.filter((selectedTag) => selectedTag !== tag);
|
||||
} else {
|
||||
return [...(tags || []), tag];
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (tag: string) => {
|
||||
const updatedSelected = getUpdatedSelected(tag);
|
||||
setTags(updatedSelected);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
setTags([]);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<BookmarkItems
|
||||
tags={tags}
|
||||
handleSubmit={handleSubmit}
|
||||
highlightSelected={true}
|
||||
header={
|
||||
<BookmarkItem
|
||||
tag="Clear all"
|
||||
data-testid="bookmark-item-clear"
|
||||
handleSubmit={clear}
|
||||
selected={false}
|
||||
icon={<CrossCircledIcon className="h-4 w-4" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkNavItems;
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useEffect, useState, useMemo, memo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useCallback, useEffect, useState, useMemo, memo } from 'react';
|
||||
import type { ConversationListResponse } from 'librechat-data-provider';
|
||||
import {
|
||||
useMediaQuery,
|
||||
useAuthContext,
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
import { useConversationsInfiniteQuery } from '~/data-provider';
|
||||
import { TooltipProvider, Tooltip } from '~/components/ui';
|
||||
import { Conversations } from '~/components/Conversations';
|
||||
import BookmarkNav from './Bookmarks/BookmarkNav';
|
||||
import { useSearchContext } from '~/Providers';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import SearchBar from './SearchBar';
|
||||
|
@ -19,7 +21,6 @@ import NavToggle from './NavToggle';
|
|||
import NavLinks from './NavLinks';
|
||||
import NewChat from './NewChat';
|
||||
import { cn } from '~/utils';
|
||||
import { ConversationListResponse } from 'librechat-data-provider';
|
||||
import store from '~/store';
|
||||
|
||||
const Nav = ({ navVisible, setNavVisible }) => {
|
||||
|
@ -58,12 +59,21 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
|||
|
||||
const { refreshConversations } = useConversations();
|
||||
const { pageNumber, searchQuery, setPageNumber, searchQueryRes } = useSearchContext();
|
||||
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery(
|
||||
{ pageNumber: pageNumber.toString(), isArchived: false },
|
||||
{ enabled: isAuthenticated },
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
|
||||
useConversationsInfiniteQuery(
|
||||
{
|
||||
pageNumber: pageNumber.toString(),
|
||||
isArchived: false,
|
||||
tags: tags.length === 0 ? undefined : tags,
|
||||
},
|
||||
{ enabled: isAuthenticated },
|
||||
);
|
||||
useEffect(() => {
|
||||
// When a tag is selected, refetch the list of conversations related to that tag
|
||||
refetch();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tags]);
|
||||
const { containerRef, moveToTop } = useNavScrolling<ConversationListResponse>({
|
||||
setShowLoading,
|
||||
hasNextPage: searchQuery ? searchQueryRes.hasNextPage : hasNextPage,
|
||||
|
@ -154,6 +164,7 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
<BookmarkNav tags={tags} setTags={setTags} />
|
||||
<NavLinks />
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -168,7 +179,7 @@ const Nav = ({ navVisible, setNavVisible }) => {
|
|||
navVisible={navVisible}
|
||||
className="fixed left-0 top-1/2 z-40 hidden md:flex"
|
||||
/>
|
||||
<div className={`nav-mask${navVisible ? ' active' : ''}`} onClick={toggleNavVisible} />
|
||||
<div className={`nav-mask${navVisible ? 'active' : ''}`} onClick={toggleNavVisible} />
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
|
72
client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx
Normal file
72
client/src/components/SidePanel/Bookmarks/BookmarkPanel.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
import { BookmarkPlusIcon } from 'lucide-react';
|
||||
import type { FC } from 'react';
|
||||
import { useConversationTagsQuery, useRebuildConversationTagsMutation } from '~/data-provider';
|
||||
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui';
|
||||
import { BookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import { BookmarkEditDialog } from '~/components/Bookmarks';
|
||||
import BookmarkTable from './BookmarkTable';
|
||||
import { Spinner } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils/';
|
||||
import HoverCardSettings from '~/components/Nav/SettingsTabs/HoverCardSettings';
|
||||
|
||||
const BookmarkPanel: FC<{ open: boolean; onOpenChange: (open: boolean) => void }> = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { mutate, isLoading } = useRebuildConversationTagsMutation();
|
||||
const { data } = useConversationTagsQuery();
|
||||
const rebuildTags = () => {
|
||||
mutate({});
|
||||
};
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
showCloseButton={true}
|
||||
className={cn(
|
||||
'overflow-x-auto shadow-2xl dark:bg-gray-700 dark:text-white md:max-h-[600px] md:min-h-[373px] md:w-[680px]',
|
||||
)}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||
{localize('com_ui_bookmarks')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<BookmarkContext.Provider value={{ bookmarks: data || [] }}>
|
||||
<div className="p-0 sm:p-6 sm:pt-4">
|
||||
<BookmarkTable />
|
||||
<div className="mt-5 sm:mt-4" />
|
||||
<div className="flex justify-between gap-2 pr-2 sm:pr-0">
|
||||
<Button variant="outline" onClick={rebuildTags} className="text-sm">
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<div className="flex gap-2">
|
||||
{localize('com_ui_bookmarks_rebuild')}
|
||||
<HoverCardSettings side="bottom" text="com_nav_info_bookmarks_rebuild" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<BookmarkEditDialog
|
||||
trigger={
|
||||
<Button variant="outline" onClick={rebuildTags} className="text-sm">
|
||||
<BookmarkPlusIcon className="mr-1 size-4" />
|
||||
<div className="break-all">{localize('com_ui_bookmarks_new')}</div>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Button variant="subtle" onClick={() => onOpenChange(!open)} className="text-sm">
|
||||
{localize('com_ui_close')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BookmarkContext.Provider>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export default BookmarkPanel;
|
59
client/src/components/SidePanel/Bookmarks/BookmarkTable.tsx
Normal file
59
client/src/components/SidePanel/Bookmarks/BookmarkTable.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import type { ConversationTagsResponse, TConversationTag } from 'librechat-data-provider';
|
||||
import { BookmarkContext, useBookmarkContext } from '~/Providers/BookmarkContext';
|
||||
import BookmarkTableRow from './BookmarkTableRow';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const BookmarkTable = () => {
|
||||
const localize = useLocalize();
|
||||
const [rows, setRows] = useState<ConversationTagsResponse>([]);
|
||||
|
||||
const { bookmarks } = useBookmarkContext();
|
||||
useEffect(() => {
|
||||
setRows(bookmarks?.map((item) => ({ id: item.tag, ...item })) || []);
|
||||
}, [bookmarks]);
|
||||
|
||||
const moveRow = useCallback((dragIndex: number, hoverIndex: number) => {
|
||||
setRows((prevTags: TConversationTag[]) => {
|
||||
const updatedRows = [...prevTags];
|
||||
const [movedRow] = updatedRows.splice(dragIndex, 1);
|
||||
updatedRows.splice(hoverIndex, 0, movedRow);
|
||||
return updatedRows;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const renderRow = useCallback((row: TConversationTag, position: number) => {
|
||||
return <BookmarkTableRow key={row.tag} moveRow={moveRow} row={row} position={position} />;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BookmarkContext.Provider value={{ bookmarks }}>
|
||||
<div
|
||||
className={cn(
|
||||
'container',
|
||||
'relative h-[300px] overflow-auto',
|
||||
'-mx-4 w-auto ring-1 ring-gray-300 sm:mx-0 sm:rounded-lg',
|
||||
)}
|
||||
>
|
||||
<table className="min-w-full divide-gray-300">
|
||||
<thead className="sticky top-0 z-10 border-b bg-white">
|
||||
<tr className="text-left text-sm font-semibold text-gray-900">
|
||||
<th className="w-96 px-3 py-3.5 pl-6">
|
||||
<div>{localize('com_ui_bookmarks_title')}</div>
|
||||
</th>
|
||||
<th className="w-28 px-3 py-3.5 sm:pl-6">
|
||||
<div>{localize('com_ui_bookmarks_count')}</div>
|
||||
</th>
|
||||
|
||||
<th className="flex-grow px-3 py-3.5 sm:pl-6"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-sm">{rows.map((row, i) => renderRow(row, i))}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</BookmarkContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkTable;
|
135
client/src/components/SidePanel/Bookmarks/BookmarkTableRow.tsx
Normal file
135
client/src/components/SidePanel/Bookmarks/BookmarkTableRow.tsx
Normal file
|
@ -0,0 +1,135 @@
|
|||
import { useRef } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import type { FC } from 'react';
|
||||
import type { Identifier, XYCoord } from 'dnd-core';
|
||||
import type { TConversationTag } from 'librechat-data-provider';
|
||||
import { DeleteBookmarkButton, EditBookmarkButton } from '~/components/Bookmarks';
|
||||
import { useConversationTagMutation } from '~/data-provider';
|
||||
import { NotificationSeverity } from '~/common';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
export const ItemTypes = {
|
||||
CARD: 'card',
|
||||
};
|
||||
|
||||
export interface BookmarkItemProps {
|
||||
position: number;
|
||||
moveRow: (dragIndex: number, hoverIndex: number) => void;
|
||||
row: TConversationTag;
|
||||
}
|
||||
|
||||
interface DragItem {
|
||||
index: number;
|
||||
id: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
const BookmarkTableRow: FC<BookmarkItemProps> = ({ position, moveRow, row, ...rest }) => {
|
||||
const ref = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
const mutation = useConversationTagMutation(row.tag);
|
||||
const localize = useLocalize();
|
||||
const { showToast } = useToastContext();
|
||||
|
||||
const handleDrop = (item: DragItem) => {
|
||||
const data = {
|
||||
...row,
|
||||
position: item.index,
|
||||
};
|
||||
mutation.mutate(data, {
|
||||
onError: () => {
|
||||
showToast({
|
||||
message: localize('com_endpoint_preset_save_error'),
|
||||
severity: NotificationSeverity.ERROR,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
|
||||
accept: ItemTypes.CARD,
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
};
|
||||
},
|
||||
drop(item: DragItem, monitor) {
|
||||
handleDrop(item);
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
const dragIndex = item.index;
|
||||
const hoverIndex = position;
|
||||
if (dragIndex === hoverIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hoverBoundingRect = ref.current?.getBoundingClientRect();
|
||||
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
|
||||
|
||||
const clientOffset = monitor.getClientOffset();
|
||||
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top;
|
||||
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return;
|
||||
}
|
||||
|
||||
moveRow(dragIndex, hoverIndex);
|
||||
|
||||
item.index = hoverIndex;
|
||||
},
|
||||
});
|
||||
|
||||
const [{ isDragging }, drag] = useDrag({
|
||||
type: ItemTypes.CARD,
|
||||
item: () => {
|
||||
return { id: row.tag, index: position };
|
||||
},
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging(),
|
||||
}),
|
||||
});
|
||||
|
||||
if (position > 0) {
|
||||
drag(drop(ref));
|
||||
}
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={cn(
|
||||
'group cursor-pointer gap-2 rounded text-sm hover:bg-black/5 focus:ring-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-white/5',
|
||||
isDragging ? 'opacity-0' : 'opacity-100',
|
||||
)}
|
||||
key={row.tag}
|
||||
ref={ref}
|
||||
data-handler-id={handlerId}
|
||||
role="menuitem"
|
||||
tabIndex={-1}
|
||||
{...rest}
|
||||
>
|
||||
<td className="w-96 py-2 pl-6 pr-3">{row.tag}</td>
|
||||
<td className={cn('w-28 py-2 pl-4 pr-3 sm:pl-6')}>
|
||||
<span className="py-1">{row.count}</span>
|
||||
</td>
|
||||
<td className="flex-grow py-2 pl-4 pr-4 sm:pl-6">
|
||||
{position > 0 && (
|
||||
<div className="flex w-full items-center justify-end gap-2 py-1 text-gray-400">
|
||||
<EditBookmarkButton bookmark={row} />
|
||||
<DeleteBookmarkButton bookmark={row.tag} />
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default BookmarkTableRow;
|
|
@ -41,9 +41,9 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
|||
? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
|
||||
: '',
|
||||
)}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick();
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
return;
|
||||
}
|
||||
|
@ -87,9 +87,9 @@ export default function Nav({ links, isCollapsed, resize, defaultActive }: NavPr
|
|||
'hover:bg-gray-50 data-[state=open]:bg-gray-50 data-[state=open]:text-black dark:hover:bg-gray-700 dark:data-[state=open]:bg-gray-700 dark:data-[state=open]:text-white',
|
||||
'w-full justify-start rounded-md border dark:border-gray-700',
|
||||
)}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (link.onClick) {
|
||||
link.onClick();
|
||||
link.onClick(e);
|
||||
setActive('');
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/compo
|
|||
import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip';
|
||||
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
|
||||
import { useMediaQuery, useLocalStorage } from '~/hooks';
|
||||
import BookmarkPanel from './Bookmarks/BookmarkPanel';
|
||||
import NavToggle from '~/components/Nav/NavToggle';
|
||||
import { useChatContext } from '~/Providers';
|
||||
import Switcher from './Switcher';
|
||||
|
@ -79,8 +80,20 @@ const SidePanel = ({
|
|||
localStorage.setItem('fullPanelCollapse', 'true');
|
||||
panelRef.current?.collapse();
|
||||
}, []);
|
||||
const [showBookmarks, setShowBookmarks] = useState(false);
|
||||
const manageBookmarks = useCallback((e) => {
|
||||
e.preventDefault();
|
||||
setShowBookmarks((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const Links = useSideNavLinks({ hidePanel, assistants, keyProvided, endpoint, interfaceConfig });
|
||||
const Links = useSideNavLinks({
|
||||
hidePanel,
|
||||
assistants,
|
||||
keyProvided,
|
||||
endpoint,
|
||||
interfaceConfig,
|
||||
manageBookmarks,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const throttledSaveLayout = useCallback(
|
||||
|
@ -128,6 +141,7 @@ const SidePanel = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
{showBookmarks && <BookmarkPanel open={showBookmarks} onOpenChange={setShowBookmarks} />}
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
|
@ -216,7 +230,7 @@ const SidePanel = ({
|
|||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
<div
|
||||
className={`nav-mask${!isCollapsed ? ' active' : ''}`}
|
||||
className={`nav-mask${!isCollapsed ? 'active' : ''}`}
|
||||
onClick={() => {
|
||||
setIsCollapsed(() => {
|
||||
localStorage.setItem('fullPanelCollapse', 'true');
|
||||
|
|
88
client/src/components/ui/TooltipIcon.tsx
Normal file
88
client/src/components/ui/TooltipIcon.tsx
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { ReactElement } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
Label,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '~/components/ui';
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import { CrossIcon } from '~/components/svg';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function TooltipIcon({
|
||||
disabled,
|
||||
appendLabel = false,
|
||||
title,
|
||||
className = '',
|
||||
confirm,
|
||||
confirmMessage,
|
||||
icon,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
title: string;
|
||||
appendLabel?: boolean;
|
||||
className?: string;
|
||||
confirm?: () => void;
|
||||
confirmMessage?: ReactElement;
|
||||
icon?: ReactElement;
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
|
||||
const renderDeleteButton = () => {
|
||||
if (appendLabel) {
|
||||
return (
|
||||
<>
|
||||
{icon} {localize('com_ui_delete')}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TooltipProvider delayDuration={250}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span>{icon}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={0}>
|
||||
{localize('com_ui_delete')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
|
||||
if (!confirmMessage) {
|
||||
return (
|
||||
<button className={className} onClick={confirm}>
|
||||
{disabled ? <CrossIcon /> : renderDeleteButton()}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className={className}>{disabled ? <CrossIcon /> : renderDeleteButton()}</button>
|
||||
</DialogTrigger>
|
||||
<DialogTemplate
|
||||
showCloseButton={false}
|
||||
title={title}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<>
|
||||
<div className="flex w-full flex-col items-center gap-2">
|
||||
<div className="grid w-full items-center gap-2">{confirmMessage}</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: confirm,
|
||||
selectClasses:
|
||||
'bg-red-700 dark:bg-red-600 hover:bg-red-800 dark:hover:bg-red-800 text-white',
|
||||
selectText: localize('com_ui_delete'),
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -3,12 +3,21 @@ import {
|
|||
LocalStorageKeys,
|
||||
InfiniteCollections,
|
||||
defaultAssistantsVersion,
|
||||
ConversationListResponse,
|
||||
} from 'librechat-data-provider';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider';
|
||||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type t from 'librechat-data-provider';
|
||||
import type { InfiniteData, UseMutationResult } from '@tanstack/react-query';
|
||||
import { updateConversationTag } from '~/utils/conversationTags';
|
||||
import { normalizeData } from '~/utils/collection';
|
||||
import store from '~/store';
|
||||
import {
|
||||
useConversationTagsQuery,
|
||||
useConversationsInfiniteQuery,
|
||||
useSharedLinksInfiniteQuery,
|
||||
} from './queries';
|
||||
import {
|
||||
/* Shared Links */
|
||||
addSharedLink,
|
||||
|
@ -19,9 +28,6 @@ import {
|
|||
updateConversation,
|
||||
deleteConversation,
|
||||
} from '~/utils';
|
||||
import { useConversationsInfiniteQuery, useSharedLinksInfiniteQuery } from './queries';
|
||||
import { normalizeData } from '~/utils/collection';
|
||||
import store from '~/store';
|
||||
|
||||
export type TGenTitleMutation = UseMutationResult<
|
||||
t.TGenTitleResponse,
|
||||
|
@ -83,6 +89,112 @@ export const useUpdateConversationMutation = (
|
|||
);
|
||||
};
|
||||
|
||||
const useUpdateTagsInConversation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Update the queryClient cache with the new tag when a new tag is added/removed to a conversation
|
||||
const updateTagsInConversation = (conversationId: string, tags: string[]) => {
|
||||
// Update the tags for the current conversation
|
||||
const currentConvo = queryClient.getQueryData<t.TConversation>([
|
||||
QueryKeys.conversation,
|
||||
conversationId,
|
||||
]);
|
||||
if (!currentConvo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedConvo = {
|
||||
...currentConvo,
|
||||
tags,
|
||||
} as t.TConversation;
|
||||
queryClient.setQueryData([QueryKeys.conversation, conversationId], updatedConvo);
|
||||
queryClient.setQueryData<t.ConversationData>([QueryKeys.allConversations], (convoData) => {
|
||||
if (!convoData) {
|
||||
return convoData;
|
||||
}
|
||||
return updateConvoFields(
|
||||
convoData,
|
||||
{
|
||||
conversationId: currentConvo.conversationId,
|
||||
tags: updatedConvo.tags,
|
||||
} as t.TConversation,
|
||||
true,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// update the tag to newTag in all conversations when a tag is updated to a newTag
|
||||
// The difference with updateTagsInConversation is that it adds or removes tags for a specific conversation,
|
||||
// whereas this function is for changing the title of a specific tag.
|
||||
const replaceTagsInAllConversations = (tag: string, newTag: string) => {
|
||||
const data = queryClient.getQueryData<InfiniteData<ConversationListResponse>>([
|
||||
QueryKeys.allConversations,
|
||||
]);
|
||||
|
||||
const conversationIdsWithTag = [] as string[];
|
||||
|
||||
// update tag to newTag in all conversations
|
||||
const newData = JSON.parse(JSON.stringify(data)) as InfiniteData<ConversationListResponse>;
|
||||
for (let pageIndex = 0; pageIndex < newData.pages.length; pageIndex++) {
|
||||
const page = newData.pages[pageIndex];
|
||||
page.conversations = page.conversations.map((conversation) => {
|
||||
if (conversation.conversationId && conversation.tags?.includes(tag)) {
|
||||
conversationIdsWithTag.push(conversation.conversationId);
|
||||
conversation.tags = conversation.tags.map((t) => (t === tag ? newTag : t));
|
||||
}
|
||||
return conversation;
|
||||
});
|
||||
}
|
||||
queryClient.setQueryData<InfiniteData<ConversationListResponse>>(
|
||||
[QueryKeys.allConversations],
|
||||
newData,
|
||||
);
|
||||
|
||||
// update the tag to newTag from the cache of each conversation
|
||||
for (let i = 0; i < conversationIdsWithTag.length; i++) {
|
||||
const conversationId = conversationIdsWithTag[i];
|
||||
const conversation = queryClient.getQueryData<t.TConversation>([
|
||||
QueryKeys.conversation,
|
||||
conversationId,
|
||||
]);
|
||||
if (conversation && conversation.tags) {
|
||||
const updatedConvo = {
|
||||
...conversation,
|
||||
tags: conversation.tags.map((t) => (t === tag ? newTag : t)),
|
||||
} as t.TConversation;
|
||||
queryClient.setQueryData<t.TConversation>(
|
||||
[QueryKeys.conversation, conversationId],
|
||||
updatedConvo,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { updateTagsInConversation, replaceTagsInAllConversations };
|
||||
};
|
||||
/**
|
||||
* Add or remove tags for a conversation
|
||||
*/
|
||||
export const useTagConversationMutation = (
|
||||
conversationId: string,
|
||||
): UseMutationResult<t.TTagConversationResponse, unknown, t.TTagConversationRequest, unknown> => {
|
||||
const query = useConversationTagsQuery();
|
||||
const { updateTagsInConversation } = useUpdateTagsInConversation();
|
||||
return useMutation(
|
||||
(payload: t.TTagConversationRequest) =>
|
||||
dataService.addTagToConversation(conversationId, payload),
|
||||
{
|
||||
onSuccess: (updatedTags) => {
|
||||
// Because the logic for calculating the bookmark count is complex,
|
||||
// the client does not perform the calculation,
|
||||
// but instead refetch the data from the API.
|
||||
query.refetch();
|
||||
updateTagsInConversation(conversationId, updatedTags);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useArchiveConversationMutation = (
|
||||
id: string,
|
||||
): UseMutationResult<
|
||||
|
@ -273,6 +385,138 @@ export const useDeleteSharedLinkMutation = (
|
|||
});
|
||||
};
|
||||
|
||||
// If the number of conversations tagged is incorrect, recalculate the tag information.
|
||||
export const useRebuildConversationTagsMutation = (): UseMutationResult<
|
||||
t.TConversationTagsResponse,
|
||||
unknown,
|
||||
unknown,
|
||||
unknown
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(() => dataService.rebuildConversationTags(), {
|
||||
onSuccess: (_data) => {
|
||||
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], _data);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Add a tag or update tag information (tag, description, position, etc.)
|
||||
export const useConversationTagMutation = (
|
||||
tag?: string,
|
||||
options?: t.UpdateConversationTagOptions,
|
||||
): UseMutationResult<t.TConversationTagResponse, unknown, t.TConversationTagRequest, unknown> => {
|
||||
const queryClient = useQueryClient();
|
||||
const { ..._options } = options || {};
|
||||
const { updateTagsInConversation, replaceTagsInAllConversations } = useUpdateTagsInConversation();
|
||||
return useMutation(
|
||||
(payload: t.TConversationTagRequest) =>
|
||||
tag
|
||||
? dataService.updateConversationTag(tag, payload)
|
||||
: dataService.createConversationTag(payload),
|
||||
{
|
||||
onSuccess: (_data, vars) => {
|
||||
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (data) => {
|
||||
if (!data) {
|
||||
return [
|
||||
{
|
||||
tag: 'Saved',
|
||||
count: 1,
|
||||
position: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
] as t.TConversationTag[];
|
||||
}
|
||||
return updateConversationTag(data, vars, _data, tag);
|
||||
});
|
||||
if (vars.addToConversation && vars.conversationId && _data.tag) {
|
||||
const currentConvo = queryClient.getQueryData<t.TConversation>([
|
||||
QueryKeys.conversation,
|
||||
vars.conversationId,
|
||||
]);
|
||||
if (!currentConvo) {
|
||||
return;
|
||||
}
|
||||
updateTagsInConversation(vars.conversationId, [...(currentConvo.tags || []), _data.tag]);
|
||||
}
|
||||
// Change the tag title to the new title
|
||||
if (tag) {
|
||||
replaceTagsInAllConversations(tag, _data.tag);
|
||||
}
|
||||
},
|
||||
...(_options || {}),
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
// When a bookmark is deleted, remove that bookmark(tag) from all conversations associated with it
|
||||
export const useDeleteTagInConversations = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const deleteTagInAllConversation = (deletedTag: string) => {
|
||||
const data = queryClient.getQueryData<InfiniteData<ConversationListResponse>>([
|
||||
QueryKeys.allConversations,
|
||||
]);
|
||||
|
||||
const conversationIdsWithTag = [] as string[];
|
||||
|
||||
// remove deleted tag from conversations
|
||||
const newData = JSON.parse(JSON.stringify(data)) as InfiniteData<ConversationListResponse>;
|
||||
for (let pageIndex = 0; pageIndex < newData.pages.length; pageIndex++) {
|
||||
const page = newData.pages[pageIndex];
|
||||
page.conversations = page.conversations.map((conversation) => {
|
||||
if (conversation.conversationId && conversation.tags?.includes(deletedTag)) {
|
||||
conversationIdsWithTag.push(conversation.conversationId);
|
||||
conversation.tags = conversation.tags.filter((t) => t !== deletedTag);
|
||||
}
|
||||
return conversation;
|
||||
});
|
||||
}
|
||||
queryClient.setQueryData<InfiniteData<ConversationListResponse>>(
|
||||
[QueryKeys.allConversations],
|
||||
newData,
|
||||
);
|
||||
|
||||
// Remove the deleted tag from the cache of each conversation
|
||||
for (let i = 0; i < conversationIdsWithTag.length; i++) {
|
||||
const conversationId = conversationIdsWithTag[i];
|
||||
const conversationData = queryClient.getQueryData<t.TConversation>([
|
||||
QueryKeys.conversation,
|
||||
conversationId,
|
||||
]);
|
||||
if (conversationData && conversationData.tags) {
|
||||
conversationData.tags = conversationData.tags.filter((t) => t !== deletedTag);
|
||||
queryClient.setQueryData<t.TConversation>(
|
||||
[QueryKeys.conversation, conversationId],
|
||||
conversationData,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
return deleteTagInAllConversation;
|
||||
};
|
||||
// Delete a tag
|
||||
export const useDeleteConversationTagMutation = (
|
||||
options?: t.DeleteConversationTagOptions,
|
||||
): UseMutationResult<t.TConversationTagResponse, unknown, string, void> => {
|
||||
const queryClient = useQueryClient();
|
||||
const deleteTagInAllConversations = useDeleteTagInConversations();
|
||||
const { onSuccess, ..._options } = options || {};
|
||||
return useMutation((tag: string) => dataService.deleteConversationTag(tag), {
|
||||
onSuccess: (_data, vars, context) => {
|
||||
queryClient.setQueryData<t.TConversationTag[]>([QueryKeys.conversationTags], (data) => {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
return data.filter((t) => t.tag !== vars);
|
||||
});
|
||||
|
||||
deleteTagInAllConversations(vars);
|
||||
onSuccess?.(_data, vars, context);
|
||||
},
|
||||
...(_options || {}),
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteConversationMutation = (
|
||||
options?: t.DeleteConversationOptions,
|
||||
): UseMutationResult<
|
||||
|
|
|
@ -150,6 +150,7 @@ export const useConversationsInfiniteQuery = (
|
|||
...params,
|
||||
pageNumber: pageParam?.toString(),
|
||||
isArchived: params?.isArchived || false,
|
||||
tags: params?.tags || [],
|
||||
}),
|
||||
{
|
||||
getNextPageParam: (lastPage) => {
|
||||
|
@ -193,6 +194,21 @@ export const useSharedLinksInfiniteQuery = (
|
|||
);
|
||||
};
|
||||
|
||||
export const useConversationTagsQuery = (
|
||||
config?: UseQueryOptions<t.TConversationTagsResponse>,
|
||||
): QueryObserverResult<t.TConversationTagsResponse> => {
|
||||
return useQuery<t.TConversationTag[]>(
|
||||
[QueryKeys.conversationTags],
|
||||
() => dataService.getConversationTags(),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* ASSISTANTS
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,7 @@ import { useMemo } from 'react';
|
|||
import {
|
||||
ArrowRightToLine,
|
||||
MessageSquareQuote,
|
||||
Bookmark,
|
||||
// Settings2,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
|
@ -25,12 +26,14 @@ export default function useSideNavLinks({
|
|||
keyProvided,
|
||||
endpoint,
|
||||
interfaceConfig,
|
||||
manageBookmarks,
|
||||
}: {
|
||||
hidePanel: () => void;
|
||||
assistants?: TConfig | null;
|
||||
keyProvided: boolean;
|
||||
endpoint?: EModelEndpoint | null;
|
||||
interfaceConfig: Partial<TInterfaceConfig>;
|
||||
manageBookmarks: (e?: React.MouseEvent) => void;
|
||||
}) {
|
||||
const hasAccessToPrompts = useHasAccess({
|
||||
permissionType: PermissionTypes.PROMPTS,
|
||||
|
@ -73,6 +76,14 @@ export default function useSideNavLinks({
|
|||
Component: FilesPanel,
|
||||
});
|
||||
|
||||
links.push({
|
||||
title: 'com_sidepanel_conversation_tags',
|
||||
label: '',
|
||||
icon: Bookmark,
|
||||
onClick: manageBookmarks,
|
||||
id: 'bookmarks',
|
||||
});
|
||||
|
||||
links.push({
|
||||
title: 'com_sidepanel_hide_panel',
|
||||
label: '',
|
||||
|
@ -89,6 +100,7 @@ export default function useSideNavLinks({
|
|||
endpoint,
|
||||
interfaceConfig.parameters,
|
||||
hasAccessToPrompts,
|
||||
manageBookmarks,
|
||||
]);
|
||||
|
||||
return Links;
|
||||
|
|
|
@ -74,6 +74,20 @@ export default {
|
|||
com_ui_unarchive: 'إلغاء الأرشفة',
|
||||
com_ui_unarchive_error: 'فشل في إلغاء الأرشفة',
|
||||
com_ui_more_options: 'المزيد',
|
||||
com_ui_bookmarks: 'الإشارات المرجعية',
|
||||
com_ui_bookmarks_rebuild: 'إعادة بناء',
|
||||
com_ui_bookmarks_new: 'إشارة مرجعية جديدة',
|
||||
com_ui_bookmark_delete_confirm: 'هل أنت متأكد أنك تريد حذف هذه الإشارة المرجعية؟',
|
||||
com_ui_bookmarks_title: 'عنوان',
|
||||
com_ui_bookmarks_count: 'العدد',
|
||||
com_ui_bookmarks_description: 'وصف',
|
||||
com_ui_bookmarks_create_success: 'تم إنشاء الإشارة المرجعية بنجاح',
|
||||
com_ui_bookmarks_update_success: 'تم تحديث الإشارة المرجعية بنجاح',
|
||||
com_ui_bookmarks_delete_success: 'تم حذف الإشارة المرجعية بنجاح',
|
||||
com_ui_bookmarks_create_error: 'حدث خطأ أثناء إنشاء الإشارة المرجعية',
|
||||
com_ui_bookmarks_update_error: 'حدث خطأ أثناء تحديث الإشارة المرجعية',
|
||||
com_ui_bookmarks_delete_error: 'حدث خطأ أثناء حذف الإشارة المرجعية',
|
||||
com_ui_bookmarks_add_to_conversation: 'أضف إلى المحادثة الحالية',
|
||||
com_auth_error_login:
|
||||
'تعذر تسجيل الدخول باستخدام المعلومات المقدمة. يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -297,6 +311,8 @@ export default {
|
|||
com_nav_help_faq: 'مساعدة & الأسئلة الشائعة',
|
||||
com_nav_settings: 'الإعدادات',
|
||||
com_nav_search_placeholder: 'بحث في الرسائل',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'إذا كان عدد الإشارات المرجعية غير صحيح، يرجى إعادة بناء معلومات الإشارة المرجعية. سيتم إعادة حساب عدد الإشارات المرجعية وستتم استعادة البيانات إلى حالتها الصحيحة.',
|
||||
com_nav_setting_general: 'عام',
|
||||
com_nav_setting_data: 'تحكم في البيانات',
|
||||
/* The following are AI translated */
|
||||
|
@ -346,6 +362,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'إخفاء اللوحة',
|
||||
com_sidepanel_attach_files: 'إرفاق الملفات',
|
||||
com_sidepanel_manage_files: 'إدارة الملفات',
|
||||
com_sidepanel_conversation_tags: 'الإشارات المرجعية',
|
||||
com_assistants_capabilities: 'قدرات',
|
||||
com_assistants_knowledge: 'المعرفة',
|
||||
com_assistants_completed_function: 'تم تشغيل {0}',
|
||||
|
@ -856,6 +873,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'المزيد',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'الإشارات المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'إعادة بناء',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'إشارة مرجعية جديدة',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'هل أنت متأكد أنك تريد حذف هذه الإشارة المرجعية؟',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'عنوان',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'العدد',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'وصف',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'تم إنشاء الإشارة المرجعية بنجاح',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'تم تحديث الإشارة المرجعية بنجاح',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'تم حذف الإشارة المرجعية بنجاح',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'حدث خطأ أثناء إنشاء الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'حدث خطأ أثناء تحديث الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'حدث خطأ أثناء حذف الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'أضف إلى المحادثة الحالية',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1652,6 +1725,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'بحث في الرسائل',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'إذا كان عدد الإشارات المرجعية غير صحيح، يرجى إعادة بناء معلومات الإشارة المرجعية. سيتم إعادة حساب عدد الإشارات المرجعية وستتم استعادة البيانات إلى حالتها الصحيحة.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'عام',
|
||||
|
@ -1832,6 +1911,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'إدارة الملفات',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'الإشارات المرجعية',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'قدرات',
|
||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Ocultar Painel',
|
||||
com_sidepanel_attach_files: 'Anexar Arquivos',
|
||||
com_sidepanel_manage_files: 'Gerenciar Arquivos',
|
||||
com_sidepanel_conversation_tags: 'Favoritos',
|
||||
com_assistants_capabilities: 'Capacidades',
|
||||
com_assistants_knowledge: 'Conhecimento',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -168,6 +169,20 @@ export default {
|
|||
'O envio de "{0}" está levando mais tempo do que o esperado. Aguarde enquanto o arquivo é indexado para recuperação.',
|
||||
com_ui_privacy_policy: 'Política de privacidade',
|
||||
com_ui_terms_of_service: 'Termos de serviço',
|
||||
com_ui_bookmarks: 'Favoritos',
|
||||
com_ui_bookmarks_rebuild: 'Reconstruir',
|
||||
com_ui_bookmarks_new: 'Novo Favorito',
|
||||
com_ui_bookmark_delete_confirm: 'Tem certeza de que deseja excluir este favorito?',
|
||||
com_ui_bookmarks_title: 'Título',
|
||||
com_ui_bookmarks_count: 'Contagem',
|
||||
com_ui_bookmarks_description: 'Descrição',
|
||||
com_ui_bookmarks_create_success: 'Favorito criado com sucesso',
|
||||
com_ui_bookmarks_update_success: 'Favorito atualizado com sucesso',
|
||||
com_ui_bookmarks_delete_success: 'Favorito excluído com sucesso',
|
||||
com_ui_bookmarks_create_error: 'Houve um erro ao criar o favorito',
|
||||
com_ui_bookmarks_update_error: 'Houve um erro ao atualizar o favorito',
|
||||
com_ui_bookmarks_delete_error: 'Houve um erro ao excluir o favorito',
|
||||
com_ui_bookmarks_add_to_conversation: 'Adicionar à conversa atual',
|
||||
com_auth_error_login:
|
||||
'Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -467,6 +482,8 @@ export default {
|
|||
com_nav_help_faq: 'Ajuda & FAQ',
|
||||
com_nav_settings: 'Configurações',
|
||||
com_nav_search_placeholder: 'Pesquisar mensagens',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Se a contagem de favoritos estiver incorreta, por favor, reconstrua as informações de favoritos. A contagem de favoritos será recalculada e os dados serão restaurados ao estado correto.',
|
||||
com_nav_setting_general: 'Geral',
|
||||
com_nav_setting_beta: 'Recursos beta',
|
||||
com_nav_setting_data: 'Controles de dados',
|
||||
|
@ -509,6 +526,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'Gerenciar Arquivos',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Favoritos',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Capacidades',
|
||||
|
@ -1103,6 +1124,62 @@ export const comparisons = {
|
|||
english: 'Terms of service',
|
||||
translated: 'Termos de serviço',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Favoritos',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Reconstruir',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Novo Favorito',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Tem certeza de que deseja excluir este favorito?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Título',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Contagem',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Descrição',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Favorito criado com sucesso',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Favorito atualizado com sucesso',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Favorito excluído com sucesso',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Houve um erro ao criar o favorito',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Houve um erro ao atualizar o favorito',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Houve um erro ao excluir o favorito',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Adicionar à conversa atual',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2160,6 +2237,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Pesquisar mensagens',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Se a contagem de favoritos estiver incorreta, por favor, reconstrua as informações de favoritos. A contagem de favoritos será recalculada e os dados serão restaurados ao estado correto.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Geral',
|
||||
|
|
|
@ -12,6 +12,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Seitenleiste ausblenden',
|
||||
com_sidepanel_attach_files: 'Dateien anhängen',
|
||||
com_sidepanel_manage_files: 'Dateien verwalten',
|
||||
com_sidepanel_conversation_tags: 'Lesezeichen',
|
||||
com_assistants_capabilities: 'Fähigkeiten',
|
||||
com_assistants_knowledge: 'Wissen',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -180,6 +181,20 @@ export default {
|
|||
'Das Hochladen von "{0}" dauert länger als erwartet. Bitte warte, während die Datei zum Abruf indiziert wird.',
|
||||
com_ui_privacy_policy: 'Datenschutzrichtlinie',
|
||||
com_ui_terms_of_service: 'Nutzungsbedingungen',
|
||||
com_ui_bookmarks: 'Lesezeichen',
|
||||
com_ui_bookmarks_rebuild: 'Neu aufbauen',
|
||||
com_ui_bookmarks_new: 'Neues Lesezeichen',
|
||||
com_ui_bookmark_delete_confirm: 'Möchten Sie dieses Lesezeichen wirklich löschen?',
|
||||
com_ui_bookmarks_title: 'Titel',
|
||||
com_ui_bookmarks_count: 'Anzahl',
|
||||
com_ui_bookmarks_description: 'Beschreibung',
|
||||
com_ui_bookmarks_create_success: 'Lesezeichen erfolgreich erstellt',
|
||||
com_ui_bookmarks_update_success: 'Lesezeichen erfolgreich aktualisiert',
|
||||
com_ui_bookmarks_delete_success: 'Lesezeichen erfolgreich gelöscht',
|
||||
com_ui_bookmarks_create_error: 'Fehler beim Erstellen des Lesezeichens',
|
||||
com_ui_bookmarks_update_error: 'Fehler beim Aktualisieren des Lesezeichens',
|
||||
com_ui_bookmarks_delete_error: 'Fehler beim Löschen des Lesezeichens',
|
||||
com_ui_bookmarks_add_to_conversation: 'Zur aktuellen Konversation hinzufügen',
|
||||
com_auth_error_login:
|
||||
'Die Anmeldung mit den angegebenen Daten ist fehlgeschlagen. Bitte überprüfe deine Anmeldedaten und versuche es erneut.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -480,6 +495,8 @@ export default {
|
|||
com_nav_help_faq: 'Hilfe & FAQ',
|
||||
com_nav_settings: 'Einstellungen',
|
||||
com_nav_search_placeholder: 'Nachrichten suchen',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Wenn die Lesezeichenanzahl falsch ist, bauen Sie bitte die Lesezeicheninformationen neu auf. Die Lesezeichenanzahl wird neu berechnet und die Daten werden in ihren korrekten Zustand wiederhergestellt.',
|
||||
com_nav_setting_general: 'Allgemein',
|
||||
com_nav_setting_beta: 'Beta-Funktionen',
|
||||
com_nav_setting_data: 'Datenkontrollen',
|
||||
|
@ -634,6 +651,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'Dateien verwalten',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Lesezeichen',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Fähigkeiten',
|
||||
|
@ -1253,6 +1274,90 @@ export const comparisons = {
|
|||
english: 'Terms of service',
|
||||
translated: 'Nutzungsbedingungen',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english:
|
||||
'Bookmarks',
|
||||
translated:
|
||||
'Lesezeichen',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english:
|
||||
'Rebuild',
|
||||
translated:
|
||||
'Neu aufbauen',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english:
|
||||
'New Bookmark',
|
||||
translated:
|
||||
'Neues Lesezeichen',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english:
|
||||
'Are you sure you want to delete this bookmark?',
|
||||
translated:
|
||||
'Möchten Sie dieses Lesezeichen wirklich löschen?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english:
|
||||
'Title',
|
||||
translated:
|
||||
'Titel',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english:
|
||||
'Count',
|
||||
translated:
|
||||
'Anzahl',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english:
|
||||
'Description',
|
||||
translated:
|
||||
'Beschreibung',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english:
|
||||
'Bookmark created successfully',
|
||||
translated:
|
||||
'Lesezeichen erfolgreich erstellt',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english:
|
||||
'Bookmark updated successfully',
|
||||
translated:
|
||||
'Lesezeichen erfolgreich aktualisiert',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english:
|
||||
'Bookmark deleted successfully',
|
||||
translated:
|
||||
'Lesezeichen erfolgreich gelöscht',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english:
|
||||
'There was an error creating the bookmark',
|
||||
translated:
|
||||
'Fehler beim Erstellen des Lesezeichens',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english:
|
||||
'There was an error updating the bookmark',
|
||||
translated:
|
||||
'Fehler beim Aktualisieren des Lesezeichens',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english:
|
||||
'There was an error deleting the bookmark',
|
||||
translated:
|
||||
'Fehler beim Löschen des Lesezeichens',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english:
|
||||
'Add to current conversation',
|
||||
translated:
|
||||
'Zur aktuellen Konversation hinzufügen',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2317,6 +2422,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Nachrichten suchen',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Wenn die Lesezeichenanzahl falsch ist, bauen Sie bitte die Lesezeicheninformationen neu auf. Die Lesezeichenanzahl wird neu berechnet und die Daten werden in ihren korrekten Zustand wiederhergestellt.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Allgemein',
|
||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Hide Panel',
|
||||
com_sidepanel_attach_files: 'Attach Files',
|
||||
com_sidepanel_manage_files: 'Manage Files',
|
||||
com_sidepanel_conversation_tags: 'Bookmarks',
|
||||
com_assistants_capabilities: 'Capabilities',
|
||||
com_assistants_file_search: 'File Search',
|
||||
com_assistants_file_search_info:
|
||||
|
@ -292,6 +293,20 @@ export default {
|
|||
com_ui_use_micrphone: 'Use microphone',
|
||||
com_ui_min_tags: 'Cannot remove more values, a minimum of {0} are required.',
|
||||
com_ui_max_tags: 'Maximum number allowed is {0}, using latest values.',
|
||||
com_ui_bookmarks: 'Bookmarks',
|
||||
com_ui_bookmarks_rebuild: 'Rebuild',
|
||||
com_ui_bookmarks_new: 'New Bookmark',
|
||||
com_ui_bookmark_delete_confirm: 'Are you sure you want to delete this bookmark?',
|
||||
com_ui_bookmarks_title: 'Title',
|
||||
com_ui_bookmarks_count: 'Count',
|
||||
com_ui_bookmarks_description: 'Description',
|
||||
com_ui_bookmarks_create_success: 'Bookmark created successfully',
|
||||
com_ui_bookmarks_update_success: 'Bookmark updated successfully',
|
||||
com_ui_bookmarks_delete_success: 'Bookmark deleted successfully',
|
||||
com_ui_bookmarks_create_error: 'There was an error creating the bookmark',
|
||||
com_ui_bookmarks_update_error: 'There was an error updating the bookmark',
|
||||
com_ui_bookmarks_delete_error: 'There was an error deleting the bookmark',
|
||||
com_ui_bookmarks_add_to_conversation: 'Add to current conversation',
|
||||
com_auth_error_login:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -664,6 +679,8 @@ export default {
|
|||
'This action will revoke and remove all the API keys that you have provided. You will need to re-enter these credentials to continue using those endpoints.',
|
||||
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_bookmarks_rebuild:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
com_nav_setting_general: 'General',
|
||||
com_nav_setting_chat: 'Chat',
|
||||
com_nav_setting_beta: 'Beta features',
|
||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Ocultar Panel',
|
||||
com_sidepanel_attach_files: 'Adjuntar Archivos',
|
||||
com_sidepanel_manage_files: 'Administrar Archivos',
|
||||
com_sidepanel_conversation_tags: 'Marcadores',
|
||||
com_assistants_capabilities: 'Capacidades',
|
||||
com_assistants_knowledge: 'Conocimiento',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -170,6 +171,20 @@ export default {
|
|||
'La carga de "{0}" está tomando más tiempo del esperado. Espere mientras el archivo termina de indexarse para su recuperación.',
|
||||
com_ui_privacy_policy: 'Política de privacidad',
|
||||
com_ui_terms_of_service: 'Términos de servicio',
|
||||
com_ui_bookmarks: 'Marcadores',
|
||||
com_ui_bookmarks_rebuild: 'Reconstruir',
|
||||
com_ui_bookmarks_new: 'Nuevo marcador',
|
||||
com_ui_bookmark_delete_confirm: '¿Está seguro de que desea eliminar este marcador?',
|
||||
com_ui_bookmarks_title: 'Título',
|
||||
com_ui_bookmarks_count: 'Conteo',
|
||||
com_ui_bookmarks_description: 'Descripción',
|
||||
com_ui_bookmarks_create_success: 'Marcador creado con éxito',
|
||||
com_ui_bookmarks_update_success: 'Marcador actualizado con éxito',
|
||||
com_ui_bookmarks_delete_success: 'Marcador eliminado con éxito',
|
||||
com_ui_bookmarks_create_error: 'Hubo un error al crear el marcador',
|
||||
com_ui_bookmarks_update_error: 'Hubo un error al actualizar el marcador',
|
||||
com_ui_bookmarks_delete_error: 'Hubo un error al eliminar el marcador',
|
||||
com_ui_bookmarks_add_to_conversation: 'Agregar a la conversación actual',
|
||||
com_auth_error_login:
|
||||
'No se puede iniciar sesión con la información proporcionada. Verifique sus credenciales y vuelva a intentarlo.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -473,6 +488,8 @@ export default {
|
|||
com_nav_help_faq: 'Ayuda y preguntas frecuentes',
|
||||
com_nav_settings: 'Configuración',
|
||||
com_nav_search_placeholder: 'Buscar mensajes',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Si el conteo de marcadores es incorrecto, por favor reconstruya la información de los marcadores. El conteo de los marcadores se recalculará y los datos se restaurarán a su estado correcto.',
|
||||
com_nav_setting_general: 'General',
|
||||
com_nav_setting_beta: 'Funciones beta',
|
||||
com_nav_setting_data: 'Controles de datos',
|
||||
|
@ -631,6 +648,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'Administrar Archivos',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Marcadores',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Capacidades',
|
||||
|
@ -1226,6 +1247,62 @@ export const comparisons = {
|
|||
english: 'Terms of service',
|
||||
translated: 'Términos de servicio',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Marcadores',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Reconstruir',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nuevo marcador',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: '¿Está seguro de que desea eliminar este marcador?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Título',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Conteo',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Descripción',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Marcador creado con éxito',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Marcador actualizado con éxito',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Marcador eliminado con éxito',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Hubo un error al crear el marcador',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Hubo un error al actualizar el marcador',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Hubo un error al eliminar el marcador',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Agregar a la conversación actual',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2292,6 +2369,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Buscar mensajes',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Si el conteo de marcadores es incorrecto, por favor reconstruya la información de los marcadores. El conteo de los marcadores se recalculará y los datos se restaurarán a su estado correcto.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'General',
|
||||
|
|
|
@ -292,6 +292,21 @@ export default {
|
|||
com_ui_use_micrphone: 'Käytä mikrofonia',
|
||||
com_ui_min_tags: 'Enempää arvoja ei voida poistaa. Niiden minimimäärä on {0}.',
|
||||
com_ui_max_tags: 'Maksimimäärä on {0}. käytetään viimeisimpiä arvoja.',
|
||||
com_ui_bookmarks: 'Kirjanmerkit',
|
||||
com_ui_bookmarks_rebuild: 'Uudelleenkokoa',
|
||||
com_ui_bookmarks_new: 'Uusi kirjanmerkki',
|
||||
com_ui_bookmark_delete_confirm:
|
||||
'Oletko varma, että haluat poistaa tämän kirjanmerkin?',
|
||||
com_ui_bookmarks_title: 'Otsikko',
|
||||
com_ui_bookmarks_count: 'Määrä',
|
||||
com_ui_bookmarks_description: 'Kuvaus',
|
||||
com_ui_bookmarks_create_success: 'Kirjanmerkki luotu onnistuneesti',
|
||||
com_ui_bookmarks_update_success: 'Kirjanmerkki päivitetty onnistuneesti',
|
||||
com_ui_bookmarks_delete_success: 'Kirjanmerkki poistettu onnistuneesti',
|
||||
com_ui_bookmarks_create_error: 'Virhe kirjanmerkin luomisessa',
|
||||
com_ui_bookmarks_update_error: 'Virhe kirjanmerkin päivittämisessä',
|
||||
com_ui_bookmarks_delete_error: 'Virhe kirjanmerkin poistamisessa',
|
||||
com_ui_bookmarks_add_to_conversation: 'Lisää nykyiseen keskusteluun',
|
||||
com_auth_error_login:
|
||||
'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -415,7 +430,7 @@ export default {
|
|||
com_endpoint_stop: 'Pysäytyssekvenssit',
|
||||
com_endpoint_stop_placeholder: 'Erota arvot toisistaan rivinvaihdoilla',
|
||||
com_endpoint_openai_max_tokens: `Valinnainen \`max_tokens\` -kenttä, joka kuvaa keskustelun vastauksessa generoitujen tokeneiden maksimimäärää.
|
||||
|
||||
|
||||
Syötteen ja vastauksen kokonaispituutta rajoittaa mallin konteksti-ikkuna. Konteksti -ikkunan koon ylittämisestä voi seurata virheitä.`,
|
||||
com_endpoint_openai_temp:
|
||||
'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.',
|
||||
|
|
|
@ -90,6 +90,20 @@ export default {
|
|||
com_ui_preview: 'Aperçu',
|
||||
com_ui_upload: 'Téléverser',
|
||||
com_ui_connect: 'Connecter',
|
||||
com_ui_bookmarks: 'Signets',
|
||||
com_ui_bookmarks_rebuild: 'Reconstruire',
|
||||
com_ui_bookmarks_new: 'Nouveau signet',
|
||||
com_ui_bookmark_delete_confirm: 'Êtes-vous sûr de vouloir supprimer ce signet?',
|
||||
com_ui_bookmarks_title: 'Titre',
|
||||
com_ui_bookmarks_count: 'Nombre',
|
||||
com_ui_bookmarks_description: 'Description',
|
||||
com_ui_bookmarks_create_success: 'Signet créé avec succès',
|
||||
com_ui_bookmarks_update_success: 'Signet mis à jour avec succès',
|
||||
com_ui_bookmarks_delete_success: 'Signet supprimé avec succès',
|
||||
com_ui_bookmarks_create_error: 'Une erreur est survenue lors de la création du signet',
|
||||
com_ui_bookmarks_update_error: 'Une erreur est survenue lors de la mise à jour du signet',
|
||||
com_ui_bookmarks_delete_error: 'Une erreur est survenue lors de la suppression du signet',
|
||||
com_ui_bookmarks_add_to_conversation: 'Ajouter à la conversation en cours',
|
||||
com_auth_error_login:
|
||||
'Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -363,6 +377,8 @@ export default {
|
|||
com_nav_help_faq: 'Aide & FAQ',
|
||||
com_nav_settings: 'Paramètres',
|
||||
com_nav_search_placeholder: 'Rechercher des messages',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Si le nombre de signets est incorrect, veuillez reconstruire les informations des signets. Le nombre de signets sera recalculé et les données seront restaurées à leur état correct.',
|
||||
com_nav_setting_general: 'Général',
|
||||
com_nav_setting_beta: 'Fonctionnalités bêta',
|
||||
com_nav_setting_data: 'Contrôles des données',
|
||||
|
@ -449,6 +465,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Masquer le panneau',
|
||||
com_sidepanel_attach_files: 'Joindre des fichiers',
|
||||
com_sidepanel_manage_files: 'Gérer les fichiers',
|
||||
com_sidepanel_conversation_tags: 'Signets',
|
||||
com_assistants_capabilities: 'Capacités des assistants',
|
||||
com_assistants_knowledge: 'Connaissances',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -1030,6 +1047,62 @@ export const comparisons = {
|
|||
english: 'Connect',
|
||||
translated: 'Connecter',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Signets',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Reconstruire',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nouveau signet',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Êtes-vous sûr de vouloir supprimer ce signet?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Titre',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Nombre',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Description',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Signet créé avec succès',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Signet mis à jour avec succès',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Signet supprimé avec succès',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Une erreur est survenue lors de la création du signet',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Une erreur est survenue lors de la mise à jour du signet',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Une erreur est survenue lors de la suppression du signet',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Ajouter à la conversation en cours',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1985,6 +2058,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Rechercher des messages',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Si le nombre de signets est incorrect, veuillez reconstruire les informations des signets. Le nombre de signets sera recalculé et les données seront restaurées à leur état correct.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Général',
|
||||
|
@ -2306,6 +2385,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'Gérer les fichiers',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Signets',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Capacités des assistants',
|
||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
|||
com_sidepanel_select_assistant: 'בחר סייען',
|
||||
com_sidepanel_assistant_builder: 'בניית סייען',
|
||||
com_sidepanel_attach_files: 'צרף קבצים',
|
||||
com_sidepanel_conversation_tags: 'סימניות',
|
||||
com_assistants_knowledge: 'ידע',
|
||||
com_assistants_knowledge_info:
|
||||
'אם אתה מעלה קבצים תחת ידע, שיחות עם ה-סייען שלך עשויות לכלול תוכן מהקובץ.',
|
||||
|
@ -119,6 +120,20 @@ export default {
|
|||
_ui_preview: 'תצוגה מקדימה',
|
||||
com_ui_upload: 'העלה',
|
||||
com_ui_connect: 'התחבר',
|
||||
com_ui_bookmarks: 'סימניות',
|
||||
com_ui_bookmarks_rebuild: 'בנה מחדש',
|
||||
com_ui_bookmarks_new: 'סימניה חדשה',
|
||||
com_ui_bookmark_delete_confirm: 'האם אתה בטוח שברצונך למחוק את הסימניה הזו?',
|
||||
com_ui_bookmarks_title: 'כותרת',
|
||||
com_ui_bookmarks_count: 'ספירה',
|
||||
com_ui_bookmarks_description: 'תיאור',
|
||||
com_ui_bookmarks_create_success: 'הסימניה נוצרה בהצלחה',
|
||||
com_ui_bookmarks_update_success: 'הסימניה עודכנה בהצלחה',
|
||||
com_ui_bookmarks_delete_success: 'הסימניה נמחקה בהצלחה',
|
||||
com_ui_bookmarks_create_error: 'אירעה שגיאה בעת יצירת הסימניה',
|
||||
com_ui_bookmarks_update_error: 'אירעה שגיאה בעת עדכון הסימניה',
|
||||
com_ui_bookmarks_delete_error: 'אירעה שגיאה בעת מחיקת הסימניה',
|
||||
com_ui_bookmarks_add_to_conversation: 'הוסף לשיחה הנוכחית',
|
||||
com_auth_error_login: 'לא ניתן להתחבר עם המידע שסופק. אנא בדוק את האישורים שלך ונסה שוב.',
|
||||
com_auth_error_login_rl: 'יותר מדי ניסיונות כניסה בזמן קצר. בבקשה נסה שוב מאוחר יותר.',
|
||||
com_auth_error_login_ban: 'החשבון שלך נחסם באופן זמני עקב הפרות של השירות שלנו.',
|
||||
|
@ -392,6 +407,8 @@ export default {
|
|||
com_nav_help_faq: 'עזרה ושאלות נפוצות',
|
||||
com_nav_settings: 'הגדרות',
|
||||
com_nav_search_placeholder: 'חפש הודעות',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'אם ספירת הסימניות שגויה, נא לבנות מחדש את המידע של הסמניות. הספירה תחושב מחדש והנתונים ישוחזרו למצב הנכון.',
|
||||
com_nav_setting_general: 'כללי',
|
||||
com_nav_setting_beta: 'תכונות ביטא',
|
||||
com_nav_setting_data: 'בקרות נתונים',
|
||||
|
@ -411,6 +428,10 @@ export const comparisons = {
|
|||
english: 'Attach Files',
|
||||
translated: 'צרף קבצים',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'סימניות',
|
||||
},
|
||||
com_assistants_knowledge: {
|
||||
english: 'Knowledge',
|
||||
translated: 'ידע',
|
||||
|
@ -842,6 +863,62 @@ export const comparisons = {
|
|||
english: 'Connect',
|
||||
translated: 'התחבר',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'סימניות',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'בנה מחדש',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'סימניה חדשה',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'האם אתה בטוח שברצונך למחו את הסימניה הזו?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'כותרת',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'ספירה',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'תיאור',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'הסימניה נוצרה בהצלחה',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'הסימניה עודכנה בהצלחה',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'הסימניה נמחקה בהצלחה',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'אירעה שגיאה בעת יצירת הסימניה',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'אירעה שגיאה בעת עדכון הסימניה',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'אירעה שגיאה בעת מחיקת הסימניה',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'הוסף לשיחה הנוכחית',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1858,6 +1935,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'חפש הודעות',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'אם ספירת הסימניות שגויה, נא לבנות מחדש את המידע של הסמניות. הספירה תחושב מחדש והנתונים ישוחזרו למצב הנכון.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'כללי',
|
||||
|
|
|
@ -88,6 +88,20 @@ export default {
|
|||
com_ui_preview: 'Pratinjau',
|
||||
com_ui_upload: 'Unggah',
|
||||
com_ui_connect: 'Hubungkan',
|
||||
com_ui_bookmarks: 'Penanda',
|
||||
com_ui_bookmarks_rebuild: 'Bangun Kembali',
|
||||
com_ui_bookmarks_new: 'Penanda Baru',
|
||||
com_ui_bookmark_delete_confirm: 'Apakah Anda yakin ingin menghapus penanda ini?',
|
||||
com_ui_bookmarks_title: 'Judul',
|
||||
com_ui_bookmarks_count: 'Jumlah',
|
||||
com_ui_bookmarks_description: 'Deskripsi',
|
||||
com_ui_bookmarks_create_success: 'Penanda berhasil dibuat',
|
||||
com_ui_bookmarks_update_success: 'Penanda berhasil diperbarui',
|
||||
com_ui_bookmarks_delete_success: 'Penanda berhasil dihapus',
|
||||
com_ui_bookmarks_create_error: 'Terjadi kesalahan saat membuat penanda',
|
||||
com_ui_bookmarks_update_error: 'Terjadi kesalahan saat memperbarui penanda',
|
||||
com_ui_bookmarks_delete_error: 'Terjadi kesalahan saat menghapus penanda',
|
||||
com_ui_bookmarks_add_to_conversation: 'Tambahkan ke percakapan saat ini',
|
||||
com_auth_error_login:
|
||||
'Tidak dapat masuk dengan informasi yang diberikan. Silakan periksa kredensial Anda dan coba lagi.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -351,6 +365,8 @@ export default {
|
|||
com_nav_help_faq: 'Bantuan & FAQ',
|
||||
com_nav_settings: 'Pengaturan',
|
||||
com_nav_search_placeholder: 'Cari pesan',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Jika jumlah penanda tidak benar, silakan bangun kembali informasi penanda. Jumlah penanda akan dihitung ulang dan data akan dipulihkan ke keadaan yang benar.',
|
||||
com_nav_setting_general: 'Umum',
|
||||
com_nav_setting_beta: 'Fitur beta',
|
||||
com_nav_setting_data: 'Kontrol data',
|
||||
|
@ -703,6 +719,62 @@ export const comparisons = {
|
|||
english: 'Connect',
|
||||
translated: 'Hubungkan',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Penanda',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Bangun Kembali',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Penanda Baru',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Apakah Anda yakin ingin menghapus penanda ini?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Judul',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Jumlah',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Deskripsi',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Penanda berhasil dibuat',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Penanda berhasil diperbarui',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Penanda berhasil dihapus',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Terjadi kesalahan saat membuat penanda',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Terjadi kesalahan saat memperbarui penanda',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Terjadi kesalahan saat menghapus penanda',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Tambahkan ke percakapan saat ini',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1660,6 +1732,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Cari pesan',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Jika jumlah penanda tidak benar, silakan bangun kembali informasi penanda. Jumlah penanda akan dihitung ulang dan data akan dipulihkan ke keadaan yang benar.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Umum',
|
||||
|
|
|
@ -19,6 +19,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'Nascondi Pannello',
|
||||
com_sidepanel_attach_files: 'Allega File',
|
||||
com_sidepanel_manage_files: 'Gestisci File',
|
||||
com_sidepanel_conversation_tags: 'Segnalibri',
|
||||
com_assistants_capabilities: 'Capacità',
|
||||
com_assistants_knowledge: 'Conoscenza',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -224,6 +225,20 @@ export default {
|
|||
com_ui_terms_of_service: 'Termini di servizio',
|
||||
com_ui_min_tags: 'Impossibile rimuovere altri valori, è richiesto un minimo di {0}.',
|
||||
com_ui_max_tags: 'Il numero massimo consentito è {0}, verranno utilizzati gli ultimi valori.',
|
||||
com_ui_bookmarks: 'Segnalibri',
|
||||
com_ui_bookmarks_rebuild: 'Ricostruisci',
|
||||
com_ui_bookmarks_new: 'Nuovo Segnalibro',
|
||||
com_ui_bookmark_delete_confirm: 'Sei sicuro di voler eliminare questo segnalibro?',
|
||||
com_ui_bookmarks_title: 'Titolo',
|
||||
com_ui_bookmarks_count: 'Conteggio',
|
||||
com_ui_bookmarks_description: 'Descrizione',
|
||||
com_ui_bookmarks_create_success: 'Segnalibro creato con successo',
|
||||
com_ui_bookmarks_update_success: 'Segnalibro aggiornato con successo',
|
||||
com_ui_bookmarks_delete_success: 'Segnalibro eliminato con successo',
|
||||
com_ui_bookmarks_create_error: 'Si è verificato un errore durante la creazione del segnalibro',
|
||||
com_ui_bookmarks_update_error: 'Si è verificato un errore durante l\'aggiornamento del segnalibro',
|
||||
com_ui_bookmarks_delete_error: 'Si è verificato un errore durante l\'eliminazione del segnalibro',
|
||||
com_ui_bookmarks_add_to_conversation: 'Aggiungi alla conversazione attuale',
|
||||
com_auth_error_login:
|
||||
'Impossibile eseguire l\'accesso con le informazioni fornite. Controlla le tue credenziali e riprova.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -535,6 +550,8 @@ export default {
|
|||
com_dialog_delete_warning: 'ATTENZIONE: Questo cancellerà permanentemente il tuo account.',
|
||||
com_dialog_delete_data_info: 'Tutti i tuoi dati verranno eliminati.',
|
||||
com_dialog_delete_help_center: 'Per più informazioni, visita il nostro centro assistenza.',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Se il conteggio dei segnalibri è errato, ricostruisci le informazioni sui segnalibri. Il conteggio dei segnalibri verrà ricalcolato e i dati verranno ripristinati al loro stato corretto.',
|
||||
com_nav_setting_general: 'Generale',
|
||||
com_nav_setting_beta: 'Funzioni Beta',
|
||||
com_nav_setting_data: 'Controlli dati',
|
||||
|
@ -662,6 +679,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'Gestisci File',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Segnalibri',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Capacità',
|
||||
|
@ -1397,6 +1418,62 @@ export const comparisons = {
|
|||
english: 'Maximum number allowed is {0}, using latest values.',
|
||||
translated: 'Il numero massimo consentito è {0}, verranno utilizzati gli ultimi valori.',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Segnalibri',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Ricostruisci',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nuovo Segnalibro',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Sei sicuro di voler eliminare questo segnalibro?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Titolo',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Conteggio',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Descrizione',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Segnalibro creato con successo',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Segnalibro aggiornato con successo',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Segnalibro eliminato con successo',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Si è verificato un errore durante la creazione del segnalibro',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Si è verificato un errore durante l\'aggiornamento del segnalibro',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Si è verificato un errore durante l\'eliminazione del segnalibro',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Aggiungi alla conversazione attuale',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2471,6 +2548,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Cerca messaggi',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Se il conteggio dei segnalibri è errato, ricostruisci le informazioni sui segnalibri. Il conteggio dei segnalibri verrà ricalcolato e i dati verranno ripristinati al loro stato corretto.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Generali',
|
||||
|
|
|
@ -18,6 +18,7 @@ export default {
|
|||
com_sidepanel_hide_panel: 'パネルを隠す',
|
||||
com_sidepanel_attach_files: 'ファイルを添付する',
|
||||
com_sidepanel_manage_files: 'ファイルを管理',
|
||||
com_sidepanel_conversation_tags: 'ブックマーク',
|
||||
com_assistants_capabilities: 'Capabilities',
|
||||
com_assistants_knowledge: 'ナレッジ',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -181,6 +182,20 @@ export default {
|
|||
com_ui_terms_of_service: '利用規約',
|
||||
com_ui_min_tags: 'これ以上の値を削除できません。少なくとも {0} が必要です。',
|
||||
com_ui_max_tags: '最新の値を使用した場合、許可される最大数は {0} です。',
|
||||
com_ui_bookmarks: 'ブックマーク',
|
||||
com_ui_bookmarks_rebuild: '再構築',
|
||||
com_ui_bookmarks_new: '新しいブックマーク',
|
||||
com_ui_bookmark_delete_confirm: 'このブックマークを削除してもよろしいですか?',
|
||||
com_ui_bookmarks_title: 'タイトル',
|
||||
com_ui_bookmarks_count: 'カウント',
|
||||
com_ui_bookmarks_description: '説明',
|
||||
com_ui_bookmarks_create_success: 'ブックマークが正常に作成されました',
|
||||
com_ui_bookmarks_update_success: 'ブックマークが正常に更新されました',
|
||||
com_ui_bookmarks_delete_success: 'ブックマークが正常に削除されました',
|
||||
com_ui_bookmarks_create_error: 'ブックマークの作成中にエラーが発生しました',
|
||||
com_ui_bookmarks_update_error: 'ブックマークの更新中にエラーが発生しました',
|
||||
com_ui_bookmarks_delete_error: 'ブックマークの削除中にエラーが発生しました',
|
||||
com_ui_bookmarks_add_to_conversation: '現在の会話に追加',
|
||||
com_auth_error_login:
|
||||
'入力された情報ではログインできませんでした。認証情報を確認した上で再度お試しください。',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -473,6 +488,8 @@ export default {
|
|||
com_nav_help_faq: 'ヘルプ & FAQ',
|
||||
com_nav_settings: '設定',
|
||||
com_nav_search_placeholder: 'メッセージ検索',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'ブックマークのカウントが正しくない場合は、ブックマークの情報を再構築してください。ブックマークのカウントが再計算され、データが正しい状態に復元されます。',
|
||||
com_nav_setting_general: '一般',
|
||||
com_nav_setting_beta: 'ベータ版の機能',
|
||||
com_nav_setting_data: 'データ管理',
|
||||
|
@ -630,6 +647,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: 'ファイルを管理',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'ブックマーク',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Capabilities',
|
||||
|
@ -1248,6 +1269,62 @@ export const comparisons = {
|
|||
english: 'Maximum number allowed is {0}, using latest values.',
|
||||
translated: '最新の値を使用した場合、許可される最大数は {0} です。',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'ブックマーク',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: '再構築',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: '新しいブックマーク',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'このブックマークを削除してもよろしいですか?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'タイトル',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'カウント',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: '説明',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'ブックマークが正常に作成されました',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'ブックマークが正常に更新されました',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'ブックマークが正常に削除されました',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'ブックマークの作成中にエラーが発生しました',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'ブックマークの更新中にエラーが発生しました',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'ブックマークの削除中にエラーが発生しました',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: '現在の会話に追加',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2303,6 +2380,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'メッセージ検索',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'ブックマークのカウントが正しくない場合は、ブックマークの情報を再構築してください。ブックマークのカウントが再計算され、データが正しい状態に復元されます。',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: '一般',
|
||||
|
|
|
@ -74,6 +74,20 @@ export default {
|
|||
com_ui_unarchive: '아카이브 해제',
|
||||
com_ui_unarchive_error: '대화 아카이브 해제 실패',
|
||||
com_ui_more_options: '더 보기',
|
||||
com_ui_bookmarks: '북마크',
|
||||
com_ui_bookmarks_rebuild: '재구축',
|
||||
com_ui_bookmarks_new: '새 북마크',
|
||||
com_ui_bookmark_delete_confirm: '이 북마크를 삭제하시겠습니까?',
|
||||
com_ui_bookmarks_title: '제목',
|
||||
com_ui_bookmarks_count: '개수',
|
||||
com_ui_bookmarks_description: '설명',
|
||||
com_ui_bookmarks_create_success: '북마크가 성공적으로 생성되었습니다',
|
||||
com_ui_bookmarks_update_success: '북마크가 성공적으로 업데이트되었습니다',
|
||||
com_ui_bookmarks_delete_success: '북마크가 성공적으로 삭제되었습니다',
|
||||
com_ui_bookmarks_create_error: '북마크 생성 중 오류가 발생했습니다',
|
||||
com_ui_bookmarks_update_error: '북마크 업데이트 중 오류가 발생했습니다',
|
||||
com_ui_bookmarks_delete_error: '북마크 삭제 중 오류가 발생했습니다',
|
||||
com_ui_bookmarks_add_to_conversation: '현재 대화에 추가',
|
||||
com_auth_error_login: '제공된 정보로 로그인할 수 없습니다. 자격 증명을 확인하고 다시 시도하세요.',
|
||||
com_auth_no_account: '계정이 없으신가요?',
|
||||
com_auth_sign_up: '가입하기',
|
||||
|
@ -279,6 +293,8 @@ export default {
|
|||
com_nav_help_faq: '도움말 및 FAQ',
|
||||
com_nav_settings: '설정',
|
||||
com_nav_search_placeholder: '메시지 검색',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'북마크 수가 정확하지 않은 경우 북마크 정보를 재구축하십시오. 북마크 수가 다시 계산되고 데이터가 올바른 상태로 복원됩니다.',
|
||||
com_nav_setting_general: '일반',
|
||||
com_nav_setting_data: '데이터 제어',
|
||||
/* The following are AI Translated */
|
||||
|
@ -328,6 +344,7 @@ export default {
|
|||
com_sidepanel_hide_panel: '패널 숨기기',
|
||||
com_sidepanel_attach_files: '파일 첨부',
|
||||
com_sidepanel_manage_files: '파일 관리',
|
||||
com_sidepanel_conversation_tags: '북마크',
|
||||
com_assistants_capabilities: '기능',
|
||||
com_assistants_knowledge: '지식',
|
||||
com_assistants_knowledge_info:
|
||||
|
@ -854,6 +871,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: '더 보기',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: '북마크',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: '재구축',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: '새 북마크',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: '이 북마크를 삭제하시겠습니까?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: '제목',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: '개수',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: '설명',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: '북마크가 성공적으로 생성되었습니다',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: '북마크가 성공적으로 업데이트되었습니다',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: '북마크가 성공적으로 삭제되었습니다',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: '북마크 생성 중 오류가 발생했습니다',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: '북마 업데이트 중 오류가 발생했습니다',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: '북마크 삭제 중 오류가 발생했습니다',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: '현재 대화에 추가',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1602,6 +1675,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: '메시지 검색',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'북마크 수가 정확하지 않은 경우 북마크 정보를 재구축하십시오. 북마크 수가 다시 계산되고 데이터가 올바른 상태로 복원됩니다.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: '일반',
|
||||
|
@ -1782,6 +1861,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: '파일 관리',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: '북마크',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: '기능',
|
||||
|
|
|
@ -80,6 +80,24 @@ export default {
|
|||
com_ui_unarchive: 'Uit archiveren',
|
||||
com_ui_unarchive_error: 'Kan conversatie niet uit archiveren',
|
||||
com_ui_more_options: 'Meer',
|
||||
com_ui_bookmarks: 'Bladwijzers',
|
||||
com_ui_bookmarks_rebuild: 'Herbouwen',
|
||||
com_ui_bookmarks_new: 'Nieuwe bladwijzer',
|
||||
com_ui_bookmark_delete_confirm:
|
||||
'Weet je zeker dat je deze bladwijzer wilt verwijderen?',
|
||||
com_ui_bookmarks_title: 'Titel',
|
||||
com_ui_bookmarks_count: 'Aantal',
|
||||
com_ui_bookmarks_description: 'Beschrijving',
|
||||
com_ui_bookmarks_create_success: 'Bladwijzer succesvol aangemaakt',
|
||||
com_ui_bookmarks_update_success: 'Bladwijzer succesvol bijgewerkt',
|
||||
com_ui_bookmarks_delete_success: 'Bladwijzer succesvol verwijderd',
|
||||
com_ui_bookmarks_create_error:
|
||||
'Er is een fout opgetreden bij het maken van de bladwijzer',
|
||||
com_ui_bookmarks_update_error:
|
||||
'Er is een fout opgetreden bij het bijwerken van de bladwijzer',
|
||||
com_ui_bookmarks_delete_error:
|
||||
'Er is een fout opgetreden bij het verwijderen van de bladwijzer',
|
||||
com_ui_bookmarks_add_to_conversation: 'Toevoegen aan huidig gesprek',
|
||||
com_auth_error_login:
|
||||
'Kan niet inloggen met de verstrekte informatie. Controleer uw referenties en probeer het opnieuw.',
|
||||
com_auth_error_login_rl: 'Te veel inlogpogingen in een korte tijd. Probeer het later nog eens.',
|
||||
|
@ -308,6 +326,8 @@ export default {
|
|||
com_nav_help_faq: 'Help & FAQ',
|
||||
com_nav_settings: 'Instellingen',
|
||||
com_nav_search_placeholder: 'Berichten doorzoeken',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Als het aantal bladwijzers onjuist is, bouw dan de bladwijzerinformatie opnieuw op. Het aantal bladwijzers zal worden herberekend en de gegevens zullen worden hersteld naar hun juiste staat.',
|
||||
com_nav_setting_general: 'Algemeen',
|
||||
com_nav_setting_data: 'Gegevensbesturing',
|
||||
};
|
||||
|
@ -610,6 +630,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'Meer',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Bladwijzers',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Herbouwen',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nieuwe bladwijzer',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Weet je zeker dat je deze bladwijzer wilt verwijderen?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Titel',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Aantal',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Beschrijving',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Bladwijzer succesvol aangemaakt',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Bladwijzer succesvol bijgewerkt',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Bladwijzer succesvol verwijderd',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Er is een fout opgetreden bij het maken van de bladwijzer',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Er is een fout opgetreden bij het bijwerken van de bladwijzer',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Er is een fout opgetreden bij het verwijderen van de bladwijzer',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Toevoegen aan huidig gesprek',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1416,6 +1492,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Berichten doorzoeken',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Als het aantal bladwijzers onjuist is, bouw dan de bladwijzerinformatie opnieuw op. Het aantal bladwijzers zal worden herberekend en de gegevens zullen worden hersteld naar hun juiste staat.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Algemeen',
|
||||
|
|
|
@ -53,6 +53,20 @@ export default {
|
|||
com_ui_unarchive: 'Przywróć z archiwum',
|
||||
com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum',
|
||||
com_ui_more_options: 'Więcej',
|
||||
com_ui_bookmarks: 'Zakładki',
|
||||
com_ui_bookmarks_rebuild: 'Przebuduj',
|
||||
com_ui_bookmarks_new: 'Nowa zakładka',
|
||||
com_ui_bookmark_delete_confirm: 'Czy na pewno chcesz usunąć tę zakładkę?',
|
||||
com_ui_bookmarks_title: 'Tytuł',
|
||||
com_ui_bookmarks_count: 'Licznik',
|
||||
com_ui_bookmarks_description: 'Opis',
|
||||
com_ui_bookmarks_create_success: 'Zakładka została pomyślnie utworzona',
|
||||
com_ui_bookmarks_update_success: 'Zakładka została pomyślnie zaktualizowana',
|
||||
com_ui_bookmarks_delete_success: 'Zakładka została pomyślnie usunięta',
|
||||
com_ui_bookmarks_create_error: 'Wystąpił błąd podczas tworzenia zakładki',
|
||||
com_ui_bookmarks_update_error: 'Wystąpił błąd podczas aktualizacji zakładki',
|
||||
com_ui_bookmarks_delete_error: 'Wystąpił błąd podczas usuwania zakładki',
|
||||
com_ui_bookmarks_add_to_conversation: 'Dodaj do bieżącej rozmowy',
|
||||
com_auth_error_login:
|
||||
'Nie udało się zalogować przy użyciu podanych danych. Sprawdź swoje dane logowania i spróbuj ponownie.',
|
||||
com_auth_no_account: 'Nie masz konta?',
|
||||
|
@ -239,6 +253,8 @@ export default {
|
|||
com_nav_help_faq: 'Pomoc i często zadawane pytania',
|
||||
com_nav_settings: 'Ustawienia',
|
||||
com_nav_search_placeholder: 'Szukaj wiadomości',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Jeśli liczba zakładek jest nieprawidłowa, przebuduj informacje o zakładkach. Liczba zakładek zostanie ponownie obliczona, a dane przywrócone do prawidłowego stanu.',
|
||||
com_nav_setting_general: 'Ogólne',
|
||||
com_ui_import_conversation: 'Importuj',
|
||||
com_ui_import_conversation_info: 'Importuj konwersacje z pliku JSON',
|
||||
|
@ -424,6 +440,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'Więcej',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Zakładki',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Przebuduj',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nowa zakładka',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Czy na pewno chcesz usunąć tę zakładkę?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Tytuł',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Licznik',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Opis',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Zakładka została pomyślnie utworzona',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Zakładka została pomyślnie zaktualizowana',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Zakładka została pomyślnie usunięta',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Wystąpił błąd podczas tworzenia zakładki',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Wystąpił błąd podczas aktualizacji zakładki',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Wystąpił błąd podczas usuwania zakładki',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Dodaj do bieżącej rozmowy',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1089,6 +1161,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Szukaj wiadomości',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Jeśli liczba zakładek jest nieprawidłowa, przebuduj informacje o zakładkach. Liczba zakładek zostanie ponownie obliczona, a dane przywrócone do prawidłowego stanu.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Ogólne',
|
||||
|
|
|
@ -90,6 +90,20 @@ export default {
|
|||
com_ui_unarchive: 'разархивировать',
|
||||
com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum',
|
||||
com_ui_more_options: 'Еще',
|
||||
com_ui_bookmarks: 'Закладки',
|
||||
com_ui_bookmarks_rebuild: 'Перестроить',
|
||||
com_ui_bookmarks_new: 'Новая закладка',
|
||||
com_ui_bookmark_delete_confirm: 'Вы уверены, что хотите удалить э<><D18D>у закладку?',
|
||||
com_ui_bookmarks_title: 'Заголовок',
|
||||
com_ui_bookmarks_count: 'Количество',
|
||||
com_ui_bookmarks_description: 'Описание',
|
||||
com_ui_bookmarks_create_success: 'Закладка успешно создана',
|
||||
com_ui_bookmarks_update_success: 'Закладка успешно обновлена',
|
||||
com_ui_bookmarks_delete_success: 'Закладка успешно удалена',
|
||||
com_ui_bookmarks_create_error: 'Произошла ошибка при создании закладки',
|
||||
com_ui_bookmarks_update_error: 'Произошла ошибка при обновлении закладки',
|
||||
com_ui_bookmarks_delete_error: 'Произошла ошибка при удалении закладки',
|
||||
com_ui_bookmarks_add_to_conversation: 'Добавить в текущий разговор',
|
||||
com_auth_error_login:
|
||||
'Не удалось войти с предоставленной информацией. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -357,6 +371,8 @@ export default {
|
|||
com_nav_help_faq: 'Помощь и Вопросы',
|
||||
com_nav_settings: 'Настройки',
|
||||
com_nav_search_placeholder: 'Поиск сообщений',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Если количество закладок некорректно, пожалуйста, перестройте информацию о закладках. Количество закладок будет пересчитано, и данные будут восстановлены до правильного состояния.',
|
||||
com_nav_setting_general: 'Общие',
|
||||
com_nav_setting_beta: 'Бета-функции',
|
||||
com_nav_setting_data: 'Управление данными',
|
||||
|
@ -431,6 +447,7 @@ export default {
|
|||
com_files_number_selected: 'Выбрано {0} из {1} файл(а/ов)',
|
||||
com_sidepanel_parameters: 'Параметры',
|
||||
com_sidepanel_hide_panel: 'Скрыть панель',
|
||||
com_sidepanel_conversation_tags: 'Закладки',
|
||||
com_assistants_capabilities: 'Возможности',
|
||||
com_assistants_image_vision: 'Анализ изображений',
|
||||
com_assistants_search_name: 'Поиск ассистентов по имени',
|
||||
|
@ -925,6 +942,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'Еще',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Закладки',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Перестроить',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Новая закладка',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Вы уверены, что хотите удалить эу закладку?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Заголовок',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Количество',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Описание',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Закладка успешно создана',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Закладка успешно обновлена',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Закладка успешно удалена',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Произошла ошибка при создании закладки',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Произошла ошибка при обновлении закладки',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Произошла ошибка при удалении закладки',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Добавить в текущий разговор',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1876,6 +1949,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Поиск сообщений',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Если количество закладок некорректно, пожалуйста, перестройте информацию о закладках. Количество закладок будет пересчитано, и данные будут восстановлены до правильного состояния.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Общие',
|
||||
|
@ -2143,6 +2222,10 @@ export const comparisons = {
|
|||
english: 'Hide Panel',
|
||||
translated: 'Скрыть панель',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Закладки',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: 'Возможности',
|
||||
|
|
|
@ -76,6 +76,20 @@ export default {
|
|||
com_ui_unarchive: 'Avarkivera',
|
||||
com_ui_unarchive_error: 'Kunde inte avarkivera chatt',
|
||||
com_ui_more_options: 'Mer',
|
||||
com_ui_bookmarks: 'Bokmärken',
|
||||
com_ui_bookmarks_rebuild: 'Återuppbygg',
|
||||
com_ui_bookmarks_new: 'Nytt Bokmärke',
|
||||
com_ui_bookmark_delete_confirm: 'Är du säker på att du vill ta bort detta bokmärke?',
|
||||
com_ui_bookmarks_title: 'Titel',
|
||||
com_ui_bookmarks_count: 'Antal',
|
||||
com_ui_bookmarks_description: 'Beskrivning',
|
||||
com_ui_bookmarks_create_success: 'Bokmärke skapat framgångsrikt',
|
||||
com_ui_bookmarks_update_success: 'Bokmärke uppdaterat framgångsrikt',
|
||||
com_ui_bookmarks_delete_success: 'Bokm<6B><6D>rke raderat framgångsrikt',
|
||||
com_ui_bookmarks_create_error: 'Ett fel uppstod vid skapandet av bokmärket',
|
||||
com_ui_bookmarks_update_error: 'Ett fel uppstod vid uppdateringen av bokmärket',
|
||||
com_ui_bookmarks_delete_error: 'Ett fel uppstod vid raderingen av bokmärket',
|
||||
com_ui_bookmarks_add_to_conversation: 'Lägg till i nuvarande konversation',
|
||||
com_auth_error_login:
|
||||
'Kunde inte logga in med den angivna informationen. Kontrollera dina uppgifter och försök igen.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -295,6 +309,8 @@ export default {
|
|||
com_nav_help_faq: 'Hjälp & Vanliga frågor',
|
||||
com_nav_settings: 'Inställningar',
|
||||
com_nav_search_placeholder: 'Sök meddelanden',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Om antalet bokmärken är felaktigt, vänligen återuppbygg informationen om bokmärkena. Antalet bokmärken kommer att omberäknas och data återställs till sitt korrekta tillstånd.',
|
||||
com_nav_setting_general: 'Allmänt',
|
||||
com_nav_setting_data: 'Datakontroller',
|
||||
};
|
||||
|
@ -589,6 +605,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'Mer',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Bokmärken',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Återuppbygg',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Nytt Bokmärke',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Är du säker på att du vill ta bort detta bokmärke?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Titel',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Antal',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Beskrivning',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Bokmärke skapat framgångsrikt',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Bokmärke uppdaterat framgångsrikt',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Bokmrke raderat framgångsrikt',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Ett fel uppstod vid skapandet av bokmärket',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Ett fel uppstod vid uppdateringen av bokmärket',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Ett fel uppstod vid raderingen av bokmärket',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Lägg till i nuvarande konversation',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1380,6 +1452,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Sök meddelanden',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Om antalet bokmärken är felaktigt, vänligen återuppbygg informationen om bokmärkena. Antalet bokmärken kommer att omberäknas och data återställs till sitt korrekta tillstånd.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Allmänt',
|
||||
|
|
|
@ -250,6 +250,20 @@ export default {
|
|||
com_ui_use_micrphone: 'Mikrofon kullan',
|
||||
com_ui_min_tags: 'Daha fazla değer kaldırılamaz, en az {0} gereklidir.',
|
||||
com_ui_max_tags: 'İzin verilen maksimum sayı {0}, en son değerler kullanılıyor.',
|
||||
com_ui_bookmarks: 'Yer İmleri',
|
||||
com_ui_bookmarks_rebuild: 'Yeniden İnşa Et',
|
||||
com_ui_bookmarks_new: 'Yeni Yer İmi',
|
||||
com_ui_bookmark_delete_confirm: 'Bu yer imini silmek istediğinizden emin misiniz?',
|
||||
com_ui_bookmarks_title: 'Başlık',
|
||||
com_ui_bookmarks_count: 'Adet',
|
||||
com_ui_bookmarks_description: 'Açıklama',
|
||||
com_ui_bookmarks_create_success: 'Yer imi başarıyla oluşturuldu',
|
||||
com_ui_bookmarks_update_success: 'Yer imi başarıyla güncellendi',
|
||||
com_ui_bookmarks_delete_success: 'Yer imi başarıyla silindi',
|
||||
com_ui_bookmarks_create_error: 'Yer imi oluşturulurken bir hata oluştu',
|
||||
com_ui_bookmarks_update_error: 'Yer imi güncellenirken bir hata oluştu',
|
||||
com_ui_bookmarks_delete_error: 'Yer imi silinirken bir hata oluştu',
|
||||
com_ui_bookmarks_add_to_conversation: 'Mevcut sohbete ekle',
|
||||
com_auth_error_login:
|
||||
'Sağlanan bilgilerle giriş yapılamıyor. Lütfen kimlik bilgilerinizi kontrol edin ve tekrar deneyin.',
|
||||
com_auth_error_login_rl:
|
||||
|
|
|
@ -78,6 +78,20 @@ export default {
|
|||
com_ui_unarchive: 'Bỏ lưu trữ',
|
||||
com_ui_unarchive_error: 'Không thể bỏ lưu trữ cuộc trò chuyện',
|
||||
com_ui_more_options: 'Thêm',
|
||||
com_ui_bookmarks: 'Dấu trang',
|
||||
com_ui_bookmarks_rebuild: 'Xây dựng lại',
|
||||
com_ui_bookmarks_new: 'Dấu trang mới',
|
||||
com_ui_bookmark_delete_confirm: 'Bạn có chắc chắn muốn xóa dấu trang này không?',
|
||||
com_ui_bookmarks_title: 'Tiêu đề',
|
||||
com_ui_bookmarks_count: 'Số lượng',
|
||||
com_ui_bookmarks_description: 'Mô tả',
|
||||
com_ui_bookmarks_create_success: 'Tạo dấu trang thành công',
|
||||
com_ui_bookmarks_update_success: 'Cập nhật dấu trang thành công',
|
||||
com_ui_bookmarks_delete_success: 'Xóa dấu trang thành công',
|
||||
com_ui_bookmarks_create_error: 'Có lỗi xảy ra khi tạo dấu trang',
|
||||
com_ui_bookmarks_update_error: 'Có lỗi xảy ra khi cập nhật dấu trang',
|
||||
com_ui_bookmarks_delete_error: 'Có lỗi xảy ra khi x<><78>a dấu trang',
|
||||
com_ui_bookmarks_add_to_conversation: 'Thêm vào cuộc hội thoại hiện tại',
|
||||
com_auth_error_login:
|
||||
'Không thể đăng nhập với thông tin được cung cấp. Vui lòng kiểm tra thông tin đăng nhập và thử lại.',
|
||||
com_auth_error_login_rl:
|
||||
|
@ -293,6 +307,8 @@ export default {
|
|||
com_nav_help_faq: 'Trợ giúp & Câu hỏi thường gặp',
|
||||
com_nav_settings: 'Cài đặt',
|
||||
com_nav_search_placeholder: 'Tìm kiếm tin nhắn',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Nếu số lượng dấu trang không chính xác, vui lòng xây dựng lại thông tin dấu trang. Số lượng dấu trang sẽ được tính lại và dữ liệu sẽ được khôi phục về trạng thái chính xác.',
|
||||
com_nav_setting_general: 'Chung',
|
||||
com_nav_setting_data: 'Kiểm soát dữ liệu',
|
||||
};
|
||||
|
@ -588,6 +604,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: 'Thêm',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: 'Dấu trang',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: 'Xây dựng lại',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: 'Dấu trang mới',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: 'Bạn có chắc chắn muốn xóa dấu trang này không?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: 'Tiêu đề',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: 'Số lượng',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: 'Mô tả',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: 'Tạo dấu trang thành công',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: 'Cập nhật dấu trang thành công',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: 'Xóa dấu trang thành công',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'Có lỗi xảy ra khi tạo dấu trang',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'Có lỗi xảy ra khi cập nhật dấu trang',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'Có lỗi xảy ra khi xa dấu trang',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'Thêm vào cuộc hội thoại hiện tại',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1361,6 +1433,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: 'Tìm kiếm tin nhắn',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'Nếu số lượng dấu trang không chính xác, vui lòng xây dựng lại thông tin dấu trang. Số lượng dấu trang sẽ được tính lại và dữ liệu sẽ được khôi phục về trạng thái chính xác.',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: 'Chung',
|
||||
|
|
|
@ -11,6 +11,7 @@ export default {
|
|||
com_sidepanel_hide_panel: '隐藏侧边栏',
|
||||
com_sidepanel_attach_files: '附加文件',
|
||||
com_sidepanel_manage_files: '管理文件',
|
||||
com_sidepanel_conversation_tags: '书签',
|
||||
com_assistants_capabilities: '功能',
|
||||
com_assistants_knowledge: '知识',
|
||||
com_assistants_knowledge_info: '如果您在“知识”中上传文件,与助手的对话可能包括文件内容。',
|
||||
|
@ -159,6 +160,20 @@ export default {
|
|||
com_ui_upload_delay: '上传 "{0}" 时比预期花了更长时间。 文件正在进行检索索引,请稍候。',
|
||||
com_ui_privacy_policy: '隐私政策',
|
||||
com_ui_terms_of_service: '服务政策',
|
||||
com_ui_bookmarks: '书签',
|
||||
com_ui_bookmarks_rebuild: '重建',
|
||||
com_ui_bookmarks_new: '新书签',
|
||||
com_ui_bookmark_delete_confirm: '你确定要删除这个书签吗?',
|
||||
com_ui_bookmarks_title: '标题',
|
||||
com_ui_bookmarks_count: '计数',
|
||||
com_ui_bookmarks_description: '描述',
|
||||
com_ui_bookmarks_create_success: '书签创建成功',
|
||||
com_ui_bookmarks_update_success: '书签更新成功',
|
||||
com_ui_bookmarks_delete_success: '书签删除成功',
|
||||
com_ui_bookmarks_create_error: '创建书签时出错',
|
||||
com_ui_bookmarks_update_error: '更新书签时出错',
|
||||
com_ui_bookmarks_delete_error: '删除书签时出错',
|
||||
com_ui_bookmarks_add_to_conversation: '添加到当前对话',
|
||||
com_auth_error_login: '无法登录,请确认提供的账户密码正确,并重新尝试。',
|
||||
com_auth_error_login_rl: '尝试登录次数过多,请稍后再试。',
|
||||
com_auth_error_login_ban: '根据我们的服务规则,您的帐号被暂时禁用。',
|
||||
|
@ -431,6 +446,8 @@ export default {
|
|||
com_nav_help_faq: '帮助',
|
||||
com_nav_settings: '设置',
|
||||
com_nav_search_placeholder: '搜索对话及对话内容',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'如果书签计数不正确,请重新构建书签信息。书签计数将被重新计算,数据将恢复到其正确状态。',
|
||||
com_nav_setting_general: '通用',
|
||||
com_nav_setting_beta: '实验特性',
|
||||
com_nav_setting_data: '数据管理',
|
||||
|
@ -572,6 +589,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: '管理文件',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: '书签',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: '功能',
|
||||
|
@ -1169,6 +1190,62 @@ export const comparisons = {
|
|||
english: 'Terms of service',
|
||||
translated: '服务政策',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: '书签',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: '重建',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: '新书签',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: '你确定要删除这个书签吗?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: '标题',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: '计数',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: '描述',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: '书签创建成功',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: '书签更新成功',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: '书签删除成功',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: '创建书签时出错',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: '更新书签时出错',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: '删除书签时出错',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: '添加到当前对话',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -2209,6 +2286,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: '搜索对话及对话内容',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'如果书签计数不正确,请重新构建书签信息。书签计数将被重新计算,数据将恢复到其正确状态。',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: '通用',
|
||||
|
|
|
@ -71,6 +71,20 @@ export default {
|
|||
com_ui_unarchive: '取消封存',
|
||||
com_ui_unarchive_error: '取消封存對話時發生錯誤',
|
||||
com_ui_more_options: '更多',
|
||||
com_ui_bookmarks: '書籤',
|
||||
com_ui_bookmarks_rebuild: '重建',
|
||||
com_ui_bookmarks_new: '新書籤',
|
||||
com_ui_bookmark_delete_confirm: '你確定要刪除這個書籤嗎?',
|
||||
com_ui_bookmarks_title: '標題',
|
||||
com_ui_bookmarks_count: '計數',
|
||||
com_ui_bookmarks_description: '描述',
|
||||
com_ui_bookmarks_create_success: '書籤創建成功',
|
||||
com_ui_bookmarks_update_success: '書籤更新成功',
|
||||
com_ui_bookmarks_delete_success: '書籤刪除成功',
|
||||
com_ui_bookmarks_create_error: '創建書籤時出錯',
|
||||
com_ui_bookmarks_update_error: '更新書籤時出錯',
|
||||
com_ui_bookmarks_delete_error: '刪除書籤時出錯',
|
||||
com_ui_bookmarks_add_to_conversation: '添加到當前對話',
|
||||
com_auth_error_login: '無法使用提供的資訊登入。請檢查您的登入資訊後重試。',
|
||||
com_auth_error_login_rl: '短時間內嘗試登入的次數過多。請稍後再試。',
|
||||
com_auth_error_login_ban: '由於違反我們的服務條款,您的帳號已被暫時停用。',
|
||||
|
@ -281,6 +295,8 @@ export default {
|
|||
com_nav_help_faq: '說明與常見問題',
|
||||
com_nav_settings: '設定',
|
||||
com_nav_search_placeholder: '搜尋訊息',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'如果書籤計數不正確,請重新構建書籤信息。書籤計數將被重新計算,數據將恢復到其正確狀態。',
|
||||
com_nav_setting_general: '一般',
|
||||
com_nav_setting_data: '資料控制',
|
||||
/* The following are AI translated */
|
||||
|
@ -328,6 +344,7 @@ export default {
|
|||
com_sidepanel_hide_panel: '隱藏側邊選單',
|
||||
com_sidepanel_attach_files: '附加檔案',
|
||||
com_sidepanel_manage_files: '管理檔案',
|
||||
com_sidepanel_conversation_tags: '書籤',
|
||||
com_assistants_capabilities: '功能',
|
||||
com_assistants_knowledge: '知識',
|
||||
com_assistants_knowledge_info: '如果您在「知識」下上傳檔案,與您的助理的對話可能會包含檔案內容。',
|
||||
|
@ -830,6 +847,62 @@ export const comparisons = {
|
|||
english: 'More',
|
||||
translated: '更多',
|
||||
},
|
||||
com_ui_bookmarks: {
|
||||
english: 'Bookmarks',
|
||||
translated: '書籤',
|
||||
},
|
||||
com_ui_bookmarks_rebuild: {
|
||||
english: 'Rebuild',
|
||||
translated: '重建',
|
||||
},
|
||||
com_ui_bookmarks_new: {
|
||||
english: 'New Bookmark',
|
||||
translated: '新書籤',
|
||||
},
|
||||
com_ui_bookmark_delete_confirm: {
|
||||
english: 'Are you sure you want to delete this bookmark?',
|
||||
translated: '你確定要刪除這個書籤嗎?',
|
||||
},
|
||||
com_ui_bookmarks_title: {
|
||||
english: 'Title',
|
||||
translated: '標題',
|
||||
},
|
||||
com_ui_bookmarks_count: {
|
||||
english: 'Count',
|
||||
translated: '計數',
|
||||
},
|
||||
com_ui_bookmarks_description: {
|
||||
english: 'Description',
|
||||
translated: '描述',
|
||||
},
|
||||
com_ui_bookmarks_create_success: {
|
||||
english: 'Bookmark created successfully',
|
||||
translated: '書籤創建成功',
|
||||
},
|
||||
com_ui_bookmarks_update_success: {
|
||||
english: 'Bookmark updated successfully',
|
||||
translated: '書籤更新成功',
|
||||
},
|
||||
com_ui_bookmarks_delete_success: {
|
||||
english: 'Bookmark deleted successfully',
|
||||
translated: '書籤刪除成功',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: '創建書籤時出錯',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: '更新書籤時出錯',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: '刪除書籤時出錯',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: '添加到當前對話',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
'Unable to login with the information provided. Please check your credentials and try again.',
|
||||
|
@ -1626,6 +1699,12 @@ export const comparisons = {
|
|||
english: 'Search messages',
|
||||
translated: '搜尋訊息',
|
||||
},
|
||||
com_nav_info_bookmarks_rebuild: {
|
||||
english:
|
||||
'If the bookmark count is incorrect, please rebuild the bookmark information. The bookmark count will be recalculated and the data will be restored to its correct state.',
|
||||
translated:
|
||||
'如果書籤計數不正確,請重新構建書籤信息。書籤計數將被重新計算,數據將恢復到其正確狀態。',
|
||||
},
|
||||
com_nav_setting_general: {
|
||||
english: 'General',
|
||||
translated: '一般',
|
||||
|
@ -1806,6 +1885,10 @@ export const comparisons = {
|
|||
english: 'Manage Files',
|
||||
translated: '管理檔案',
|
||||
},
|
||||
com_sidepanel_conversation_tags: {
|
||||
english: 'Bookmarks',
|
||||
translated: '書籤',
|
||||
},
|
||||
com_assistants_capabilities: {
|
||||
english: 'Capabilities',
|
||||
translated: '功能',
|
||||
|
|
225
client/src/utils/conversationTags.spec.ts
Normal file
225
client/src/utils/conversationTags.spec.ts
Normal file
|
@ -0,0 +1,225 @@
|
|||
import type { TConversationTagsResponse } from 'librechat-data-provider';
|
||||
import { updateConversationTag } from './conversationTags';
|
||||
|
||||
describe('ConversationTag Utilities', () => {
|
||||
let conversations: TConversationTagsResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
conversations = [
|
||||
{
|
||||
tag: 'saved',
|
||||
count: 1,
|
||||
position: 0,
|
||||
description: 'description1',
|
||||
updatedAt: '2023-04-01T12:00:00Z',
|
||||
createdAt: '2023-04-01T12:00:00Z',
|
||||
user: 'user1',
|
||||
},
|
||||
{
|
||||
tag: 'tag1',
|
||||
count: 1,
|
||||
position: 1,
|
||||
description: 'description1',
|
||||
updatedAt: '2023-04-01T12:00:00Z',
|
||||
createdAt: '2023-04-01T12:00:00Z',
|
||||
user: 'user1',
|
||||
},
|
||||
{
|
||||
tag: 'tag2',
|
||||
count: 20,
|
||||
position: 2,
|
||||
description: 'description2',
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: '2023-04-01T12:00:00Z',
|
||||
user: 'user1',
|
||||
},
|
||||
{
|
||||
tag: 'tag3',
|
||||
count: 30,
|
||||
position: 3,
|
||||
description: 'description3',
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
user: 'user1',
|
||||
},
|
||||
{
|
||||
tag: 'tag4',
|
||||
count: 40,
|
||||
position: 4,
|
||||
description: 'description4',
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
user: 'user1',
|
||||
},
|
||||
{
|
||||
tag: 'tag5',
|
||||
count: 50,
|
||||
position: 5,
|
||||
description: 'description5',
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
user: 'user1',
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
describe('updateConversationTag', () => {
|
||||
it('updates the first tag correctly', () => {
|
||||
const updated = updateConversationTag(
|
||||
conversations,
|
||||
{ tag: 'tag1-new', description: 'description1-new' },
|
||||
{
|
||||
...conversations[1],
|
||||
tag: 'tag1-new',
|
||||
description: 'description1-new',
|
||||
},
|
||||
'tag1',
|
||||
);
|
||||
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
expect(updated[1].tag).toBe('tag1-new');
|
||||
expect(updated[1].description).toBe('description1-new');
|
||||
expect(updated[1].position).toBe(1);
|
||||
expect(updated[2].tag).toBe('tag2');
|
||||
expect(updated[2].position).toBe(2);
|
||||
expect(updated[3].tag).toBe('tag3');
|
||||
expect(updated[3].position).toBe(3);
|
||||
expect(updated[4].tag).toBe('tag4');
|
||||
expect(updated[4].position).toBe(4);
|
||||
expect(updated[5].tag).toBe('tag5');
|
||||
expect(updated[5].position).toBe(5);
|
||||
});
|
||||
});
|
||||
it('updates the third tag correctly', () => {
|
||||
const updated = updateConversationTag(
|
||||
conversations,
|
||||
{ tag: 'tag3-new', description: 'description3-new' },
|
||||
{
|
||||
...conversations[3],
|
||||
tag: 'tag3-new',
|
||||
description: 'description3-new',
|
||||
},
|
||||
'tag3',
|
||||
);
|
||||
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
expect(updated[1].tag).toBe('tag1');
|
||||
expect(updated[1].position).toBe(1);
|
||||
expect(updated[2].tag).toBe('tag2');
|
||||
expect(updated[2].position).toBe(2);
|
||||
expect(updated[3].tag).toBe('tag3-new');
|
||||
expect(updated[3].description).toBe('description3-new');
|
||||
expect(updated[3].position).toBe(3);
|
||||
expect(updated[4].tag).toBe('tag4');
|
||||
expect(updated[4].position).toBe(4);
|
||||
expect(updated[5].tag).toBe('tag5');
|
||||
expect(updated[5].position).toBe(5);
|
||||
});
|
||||
|
||||
it('updates the order of other tags if the order of the tags is moving up', () => {
|
||||
const updated = updateConversationTag(
|
||||
conversations,
|
||||
// move tag3 to the second position
|
||||
{ position: 2 },
|
||||
{
|
||||
...conversations[3],
|
||||
position: 2,
|
||||
},
|
||||
'tag3',
|
||||
);
|
||||
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
expect(updated[1].tag).toBe('tag1');
|
||||
expect(updated[1].position).toBe(1);
|
||||
expect(updated[2].tag).toBe('tag3');
|
||||
expect(updated[2].position).toBe(2);
|
||||
expect(updated[3].tag).toBe('tag2');
|
||||
expect(updated[3].position).toBe(3);
|
||||
expect(updated[4].tag).toBe('tag4');
|
||||
expect(updated[4].position).toBe(4);
|
||||
expect(updated[5].tag).toBe('tag5');
|
||||
expect(updated[5].position).toBe(5);
|
||||
});
|
||||
|
||||
it('updates the order of other tags if the order of the tags is moving down', () => {
|
||||
const updated = updateConversationTag(
|
||||
conversations,
|
||||
// move tag3 to the last position
|
||||
{ position: 5 },
|
||||
{
|
||||
...conversations[3],
|
||||
position: 5,
|
||||
},
|
||||
'tag3',
|
||||
);
|
||||
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
expect(updated[1].tag).toBe('tag1');
|
||||
expect(updated[1].position).toBe(1);
|
||||
expect(updated[2].tag).toBe('tag2');
|
||||
expect(updated[2].position).toBe(2);
|
||||
expect(updated[3].tag).toBe('tag4');
|
||||
expect(updated[3].position).toBe(3);
|
||||
expect(updated[4].tag).toBe('tag5');
|
||||
expect(updated[4].position).toBe(4);
|
||||
expect(updated[5].tag).toBe('tag3');
|
||||
expect(updated[5].position).toBe(5);
|
||||
});
|
||||
|
||||
it('updates the order of other tags if new tag is added', () => {
|
||||
const updated = updateConversationTag(
|
||||
conversations,
|
||||
{ tag: 'newtag', description: 'newDescription' },
|
||||
{
|
||||
tag: 'newtag',
|
||||
description: 'newDescription',
|
||||
position: 1,
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
user: 'user1',
|
||||
count: 30,
|
||||
},
|
||||
// no tag tag specified
|
||||
);
|
||||
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
expect(updated[1].tag).toBe('newtag');
|
||||
expect(updated[1].description).toBe('newDescription');
|
||||
expect(updated[1].position).toBe(1);
|
||||
expect(updated[2].tag).toBe('tag1');
|
||||
expect(updated[2].position).toBe(2);
|
||||
expect(updated[3].tag).toBe('tag2');
|
||||
expect(updated[3].position).toBe(3);
|
||||
expect(updated[4].tag).toBe('tag3');
|
||||
expect(updated[4].position).toBe(4);
|
||||
expect(updated[5].tag).toBe('tag4');
|
||||
expect(updated[5].position).toBe(5);
|
||||
expect(updated[6].tag).toBe('tag5');
|
||||
expect(updated[6].position).toBe(6);
|
||||
});
|
||||
|
||||
it('returns a new array for new tag if no tags exist', () => {
|
||||
const updated = updateConversationTag(
|
||||
[],
|
||||
{ tag: 'newtag', description: 'newDescription' },
|
||||
{
|
||||
tag: 'saved',
|
||||
description: 'newDescription',
|
||||
position: 0,
|
||||
updatedAt: new Date().toISOString(),
|
||||
createdAt: new Date().toISOString(),
|
||||
user: 'user1',
|
||||
count: 30,
|
||||
},
|
||||
// no tag tag specified
|
||||
);
|
||||
expect(updated.length).toBe(1);
|
||||
expect(updated[0].tag).toBe('saved');
|
||||
expect(updated[0].position).toBe(0);
|
||||
});
|
||||
});
|
55
client/src/utils/conversationTags.ts
Normal file
55
client/src/utils/conversationTags.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import {
|
||||
TConversationTagRequest,
|
||||
TConversationTagResponse,
|
||||
TConversationTagsResponse,
|
||||
} from 'librechat-data-provider';
|
||||
|
||||
export const updateConversationTag = (
|
||||
queryCache: TConversationTagsResponse,
|
||||
request: TConversationTagRequest,
|
||||
response: TConversationTagResponse,
|
||||
tag?: string,
|
||||
): TConversationTagsResponse => {
|
||||
if (queryCache.length === 0) {
|
||||
return [response];
|
||||
}
|
||||
const oldData = queryCache.find((t) => t.tag === tag);
|
||||
if (!oldData) {
|
||||
// When a new tag is added, it is positioned at the top of the list.
|
||||
return [queryCache[0], response, ...queryCache.slice(1)].map((t, index) => ({
|
||||
...t,
|
||||
position: index,
|
||||
}));
|
||||
}
|
||||
|
||||
const oldPosition = oldData.position;
|
||||
const newPosition = response.position;
|
||||
|
||||
// Remove the updated data from the array
|
||||
const filteredData = queryCache.filter((t) => t.tag !== tag);
|
||||
|
||||
if (newPosition === undefined || oldPosition === newPosition) {
|
||||
// If the position hasn't changed, just replace the updated tag
|
||||
return queryCache.map((t) => (t.tag === tag ? response : t));
|
||||
}
|
||||
|
||||
// If the position has changed, update the position of the tag
|
||||
const newData = [
|
||||
...filteredData.slice(0, newPosition),
|
||||
response,
|
||||
...filteredData.slice(newPosition),
|
||||
];
|
||||
|
||||
if (newPosition > oldPosition) {
|
||||
// moving down
|
||||
for (let i = oldPosition; i < newPosition; i++) {
|
||||
newData[i].position = i;
|
||||
}
|
||||
} else {
|
||||
// moving up
|
||||
for (let i = newPosition + 1; i < newData.length; i++) {
|
||||
newData[i].position = i;
|
||||
}
|
||||
}
|
||||
return newData;
|
||||
};
|
|
@ -145,25 +145,37 @@ export const updateConversation = (
|
|||
);
|
||||
};
|
||||
|
||||
export const updateConvoFields: ConversationUpdater = (
|
||||
export const updateConvoFields = (
|
||||
data: ConversationData,
|
||||
updatedConversation: Partial<TConversation> & Pick<TConversation, 'conversationId'>,
|
||||
keepPosition = false,
|
||||
): ConversationData => {
|
||||
const newData = JSON.parse(JSON.stringify(data));
|
||||
const { pageIndex, index } = findPageForConversation(
|
||||
newData,
|
||||
updatedConversation as { conversationId: string },
|
||||
);
|
||||
|
||||
if (pageIndex !== -1 && index !== -1) {
|
||||
const deleted = newData.pages[pageIndex].conversations.splice(index, 1);
|
||||
const oldConversation = deleted[0] as TConversation;
|
||||
const oldConversation = newData.pages[pageIndex].conversations[index] as TConversation;
|
||||
|
||||
newData.pages[0].conversations.unshift({
|
||||
...oldConversation,
|
||||
...updatedConversation,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
/**
|
||||
* Do not change the position of the conversation if the tags are updated.
|
||||
*/
|
||||
if (keepPosition) {
|
||||
const updatedConvo = {
|
||||
...oldConversation,
|
||||
...updatedConversation,
|
||||
};
|
||||
newData.pages[pageIndex].conversations[index] = updatedConvo;
|
||||
} else {
|
||||
const updatedConvo = {
|
||||
...oldConversation,
|
||||
...updatedConversation,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
newData.pages[pageIndex].conversations.splice(index, 1);
|
||||
newData.pages[0].conversations.unshift(updatedConvo);
|
||||
}
|
||||
}
|
||||
|
||||
return newData;
|
||||
|
|
|
@ -32,8 +32,10 @@ export const abortRequest = (endpoint: string) => `/api/ask/${endpoint}/abort`;
|
|||
|
||||
export const conversationsRoot = '/api/convos';
|
||||
|
||||
export const conversations = (pageNumber: string, isArchived?: boolean) =>
|
||||
`${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}`;
|
||||
export const conversations = (pageNumber: string, isArchived?: boolean, tags?: string[]) =>
|
||||
`${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}${tags
|
||||
?.map((tag) => `&tags=${tag}`)
|
||||
.join('')}`;
|
||||
|
||||
export const conversationById = (id: string) => `${conversationsRoot}/${id}`;
|
||||
|
||||
|
@ -188,3 +190,14 @@ export const roles = () => '/api/roles';
|
|||
export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
|
||||
export const updatePromptPermissions = (roleName: string) =>
|
||||
`${roles()}/${roleName.toLowerCase()}/prompts`;
|
||||
|
||||
/* Conversation Tags */
|
||||
export const conversationTags = (tag?: string) => `/api/tags${tag ? `/${tag}` : ''}`;
|
||||
|
||||
export const conversationTagsList = (pageNumber: string, sort?: string, order?: string) =>
|
||||
`${conversationTags()}/list?pageNumber=${pageNumber}${sort ? `&sort=${sort}` : ''}${
|
||||
order ? `&order=${order}` : ''
|
||||
}`;
|
||||
|
||||
export const addTagToConversation = (conversationId: string) =>
|
||||
`${conversationsRoot}/tags/${conversationId}`;
|
||||
|
|
|
@ -424,7 +424,8 @@ export const listConversations = (
|
|||
// Assuming params has a pageNumber property
|
||||
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
|
||||
const isArchived = params?.isArchived || false; // Default to false if not provided
|
||||
return request.get(endpoints.conversations(pageNumber, isArchived));
|
||||
const tags = params?.tags || []; // Default to an empty array if not provided
|
||||
return request.get(endpoints.conversations(pageNumber, isArchived, tags));
|
||||
};
|
||||
|
||||
export const listConversationsByQuery = (
|
||||
|
@ -541,3 +542,34 @@ export function updatePromptPermissions(
|
|||
): Promise<m.UpdatePromptPermResponse> {
|
||||
return request.put(endpoints.updatePromptPermissions(variables.roleName), variables.updates);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
export function getConversationTags(): Promise<t.TConversationTagsResponse> {
|
||||
return request.get(endpoints.conversationTags());
|
||||
}
|
||||
|
||||
export function createConversationTag(
|
||||
payload: t.TConversationTagRequest,
|
||||
): Promise<t.TConversationTagResponse> {
|
||||
return request.post(endpoints.conversationTags(), payload);
|
||||
}
|
||||
|
||||
export function updateConversationTag(
|
||||
tag: string,
|
||||
payload: t.TConversationTagRequest,
|
||||
): Promise<t.TConversationTagResponse> {
|
||||
return request.put(endpoints.conversationTags(tag), payload);
|
||||
}
|
||||
export function deleteConversationTag(tag: string): Promise<t.TConversationTagResponse> {
|
||||
return request.delete(endpoints.conversationTags(tag));
|
||||
}
|
||||
|
||||
export function addTagToConversation(
|
||||
conversationId: string,
|
||||
payload: t.TTagConversationRequest,
|
||||
): Promise<t.TTagConversationResponse> {
|
||||
return request.put(endpoints.addTagToConversation(conversationId), payload);
|
||||
}
|
||||
export function rebuildConversationTags(): Promise<t.TConversationTagsResponse> {
|
||||
return request.post(endpoints.conversationTags('rebuild'));
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ export enum QueryKeys {
|
|||
categories = 'categories',
|
||||
randomPrompts = 'randomPrompts',
|
||||
roles = 'roles',
|
||||
conversationTags = 'conversationTags',
|
||||
}
|
||||
|
||||
export enum MutationKeys {
|
||||
|
|
|
@ -74,6 +74,21 @@ export const useGetSharedMessages = (
|
|||
);
|
||||
};
|
||||
|
||||
export const useGetConversationTags = (
|
||||
config?: UseQueryOptions<t.TConversationTagsResponse>,
|
||||
): QueryObserverResult<t.TConversationTagsResponse> => {
|
||||
return useQuery<t.TConversationTagsResponse>(
|
||||
[QueryKeys.conversationTags],
|
||||
() => dataService.getConversationTags(),
|
||||
{
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
...config,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useGetUserBalance = (
|
||||
config?: UseQueryOptions<string>,
|
||||
): QueryObserverResult<string> => {
|
||||
|
|
|
@ -371,6 +371,7 @@ export const tConversationSchema = z.object({
|
|||
updatedAt: z.string(),
|
||||
modelLabel: z.string().nullable().optional(),
|
||||
examples: z.array(tExampleSchema).optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
/* Prefer modelLabel over chatGptLabel */
|
||||
chatGptLabel: z.string().nullable().optional(),
|
||||
userLabel: z.string().optional(),
|
||||
|
@ -476,6 +477,17 @@ export const tSharedLinkSchema = z.object({
|
|||
});
|
||||
export type TSharedLink = z.infer<typeof tSharedLinkSchema>;
|
||||
|
||||
export const tConversationTagSchema = z.object({
|
||||
user: z.string(),
|
||||
tag: z.string(),
|
||||
description: z.string().optional(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
count: z.number(),
|
||||
position: z.number(),
|
||||
});
|
||||
export type TConversationTag = z.infer<typeof tConversationTagSchema>;
|
||||
|
||||
export const openAISchema = tConversationSchema
|
||||
.pick({
|
||||
model: true,
|
||||
|
|
|
@ -7,6 +7,7 @@ import type {
|
|||
TSharedLink,
|
||||
TConversation,
|
||||
EModelEndpoint,
|
||||
TConversationTag,
|
||||
} from './schemas';
|
||||
import type { TSpecsConfig } from './models';
|
||||
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
|
||||
|
@ -170,6 +171,25 @@ export type TSharedLinkResponse = TSharedLink;
|
|||
export type TSharedLinksResponse = TSharedLink[];
|
||||
export type TDeleteSharedLinkResponse = TSharedLink;
|
||||
|
||||
// type for getting conversation tags
|
||||
export type TConversationTagsResponse = TConversationTag[];
|
||||
// type for creating conversation tag
|
||||
export type TConversationTagRequest = Partial<
|
||||
Omit<TConversationTag, 'createdAt' | 'updatedAt' | 'count' | 'user'>
|
||||
> & {
|
||||
conversationId?: string;
|
||||
addToConversation?: boolean;
|
||||
};
|
||||
|
||||
export type TConversationTagResponse = TConversationTag;
|
||||
|
||||
// type for tagging conversation
|
||||
export type TTagConversationRequest = {
|
||||
conversationId: string;
|
||||
tags: string[];
|
||||
};
|
||||
export type TTagConversationResponse = string[];
|
||||
|
||||
export type TForkConvoRequest = {
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
|
|
|
@ -173,3 +173,9 @@ export type UpdatePromptPermOptions = MutationOptions<
|
|||
unknown,
|
||||
types.TError
|
||||
>;
|
||||
|
||||
export type UpdateConversationTagOptions = MutationOptions<
|
||||
types.TConversationTag,
|
||||
types.TConversationTagRequest
|
||||
>;
|
||||
export type DeleteConversationTagOptions = MutationOptions<types.TConversationTag, string>;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type { TMessage, TConversation, TSharedLink } from '../schemas';
|
||||
import type * as t from '../types';
|
||||
import type { TMessage, TConversation, TSharedLink, TConversationTag } from '../schemas';
|
||||
|
||||
export type Conversation = {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
|
@ -18,6 +19,7 @@ export type ConversationListParams = {
|
|||
pageNumber: string; // Add this line
|
||||
conversationId?: string;
|
||||
isArchived?: boolean;
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
// Type for the response from the conversation list API
|
||||
|
@ -68,3 +70,5 @@ export type AllPromptGroupsFilterRequest = {
|
|||
};
|
||||
|
||||
export type AllPromptGroupsResponse = t.TPromptGroup[];
|
||||
|
||||
export type ConversationTagsResponse = TConversationTag[];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue