🔖 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:
Yuichi Oneda 2024-07-29 07:45:59 -07:00 committed by GitHub
parent d4d56281e3
commit e565e0faab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 3751 additions and 36 deletions

View file

@ -73,13 +73,16 @@ module.exports = {
throw new Error('Failed to save conversations in bulk.'); 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 }; const query = { user };
if (isArchived) { if (isArchived) {
query.isArchived = true; query.isArchived = true;
} else { } else {
query.$or = [{ isArchived: false }, { isArchived: { $exists: false } }]; query.$or = [{ isArchived: false }, { isArchived: { $exists: false } }];
} }
if (Array.isArray(tags) && tags.length > 0) {
query.tags = { $in: tags };
}
try { try {
const totalConvos = (await Conversation.countDocuments(query)) || 1; const totalConvos = (await Conversation.countDocuments(query)) || 1;
const totalPages = Math.ceil(totalConvos / pageSize); const totalPages = Math.ceil(totalConvos / pageSize);

View 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' };
}
},
};

View 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);

View file

@ -42,6 +42,11 @@ const convoSchema = mongoose.Schema(
invocationId: { invocationId: {
type: Number, type: Number,
}, },
tags: {
type: [String],
default: [],
meiliIndex: true,
},
}, },
{ timestamps: true }, { timestamps: true },
); );

View file

@ -103,6 +103,10 @@ const conversationPreset = {
spec: { spec: {
type: String, type: String,
}, },
tags: {
type: [String],
default: [],
},
tools: { type: [{ type: String }], default: undefined }, tools: { type: [{ type: String }], default: undefined },
maxContextTokens: { maxContextTokens: {
type: Number, type: Number,

View file

@ -94,6 +94,7 @@ const startServer = async () => {
app.use('/api/share', routes.share); app.use('/api/share', routes.share);
app.use('/api/roles', routes.roles); app.use('/api/roles', routes.roles);
app.use('/api/tags', routes.tags);
app.use((req, res) => { app.use((req, res) => {
res.sendFile(path.join(app.locals.paths.dist, 'index.html')); res.sendFile(path.join(app.locals.paths.dist, 'index.html'));
}); });

View file

@ -8,6 +8,7 @@ const requireJwtAuth = require('~/server/middleware/requireJwtAuth');
const { forkConversation } = require('~/server/utils/import/fork'); const { forkConversation } = require('~/server/utils/import/fork');
const { importConversations } = require('~/server/utils/import'); const { importConversations } = require('~/server/utils/import');
const { createImportLimiters } = require('~/server/middleware'); const { createImportLimiters } = require('~/server/middleware');
const { updateTagsForConversation } = require('~/models/ConversationTag');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const { sleep } = require('~/server/utils'); const { sleep } = require('~/server/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');
@ -30,8 +31,13 @@ router.get('/', async (req, res) => {
return res.status(400).json({ error: 'Invalid page size' }); return res.status(400).json({ error: 'Invalid page size' });
} }
const isArchived = req.query.isArchived === 'true'; 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) => { 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; module.exports = router;

View file

@ -21,6 +21,7 @@ const staticRoute = require('./static');
const share = require('./share'); const share = require('./share');
const categories = require('./categories'); const categories = require('./categories');
const roles = require('./roles'); const roles = require('./roles');
const tags = require('./tags');
module.exports = { module.exports = {
search, search,
@ -46,4 +47,5 @@ module.exports = {
share, share,
categories, categories,
roles, roles,
tags,
}; };

44
api/server/routes/tags.js Normal file
View 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;

View 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);

View file

@ -7,6 +7,7 @@ export * from './SearchContext';
export * from './FileMapContext'; export * from './FileMapContext';
export * from './AddedChatContext'; export * from './AddedChatContext';
export * from './ChatFormContext'; export * from './ChatFormContext';
export * from './BookmarkContext';
export * from './DashboardContext'; export * from './DashboardContext';
export * from './AssistantsContext'; export * from './AssistantsContext';
export * from './AssistantsMapContext'; export * from './AssistantsMapContext';

View file

@ -83,7 +83,7 @@ export type NavLink = {
label?: string; label?: string;
icon: LucideIcon | React.FC; icon: LucideIcon | React.FC;
Component?: React.ComponentType; Component?: React.ComponentType;
onClick?: () => void; onClick?: (e?: React.MouseEvent) => void;
variant?: 'default' | 'ghost'; variant?: 'default' | 'ghost';
id: string; id: string;
}; };

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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';

View file

@ -6,6 +6,7 @@ import type { ContextType } from '~/common';
import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus'; import { EndpointsMenu, ModelSpecsMenu, PresetsMenu, HeaderNewChat } from './Menus';
import ExportAndShareMenu from './ExportAndShareMenu'; import ExportAndShareMenu from './ExportAndShareMenu';
import HeaderOptions from './Input/HeaderOptions'; import HeaderOptions from './Input/HeaderOptions';
import BookmarkMenu from './Menus/BookmarkMenu';
import AddMultiConvo from './AddMultiConvo'; import AddMultiConvo from './AddMultiConvo';
import { useMediaQuery } from '~/hooks'; import { useMediaQuery } from '~/hooks';
@ -37,6 +38,7 @@ export default function Header() {
className="pl-0" className="pl-0"
/> />
)} )}
<BookmarkMenu />
<AddMultiConvo /> <AddMultiConvo />
</div> </div>
{!isSmallScreen && ( {!isSmallScreen && (

View 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;

View file

@ -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>
}
/>
);
};

View 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;

View 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;

View file

@ -1,6 +1,7 @@
import { useCallback, useEffect, useState, useMemo, memo } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { useCallback, useEffect, useState, useMemo, memo } from 'react'; import type { ConversationListResponse } from 'librechat-data-provider';
import { import {
useMediaQuery, useMediaQuery,
useAuthContext, useAuthContext,
@ -12,6 +13,7 @@ import {
import { useConversationsInfiniteQuery } from '~/data-provider'; import { useConversationsInfiniteQuery } from '~/data-provider';
import { TooltipProvider, Tooltip } from '~/components/ui'; import { TooltipProvider, Tooltip } from '~/components/ui';
import { Conversations } from '~/components/Conversations'; import { Conversations } from '~/components/Conversations';
import BookmarkNav from './Bookmarks/BookmarkNav';
import { useSearchContext } from '~/Providers'; import { useSearchContext } from '~/Providers';
import { Spinner } from '~/components/svg'; import { Spinner } from '~/components/svg';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
@ -19,7 +21,6 @@ import NavToggle from './NavToggle';
import NavLinks from './NavLinks'; import NavLinks from './NavLinks';
import NewChat from './NewChat'; import NewChat from './NewChat';
import { cn } from '~/utils'; import { cn } from '~/utils';
import { ConversationListResponse } from 'librechat-data-provider';
import store from '~/store'; import store from '~/store';
const Nav = ({ navVisible, setNavVisible }) => { const Nav = ({ navVisible, setNavVisible }) => {
@ -58,12 +59,21 @@ const Nav = ({ navVisible, setNavVisible }) => {
const { refreshConversations } = useConversations(); const { refreshConversations } = useConversations();
const { pageNumber, searchQuery, setPageNumber, searchQueryRes } = useSearchContext(); const { pageNumber, searchQuery, setPageNumber, searchQueryRes } = useSearchContext();
const [tags, setTags] = useState<string[]>([]);
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useConversationsInfiniteQuery( const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch } =
{ pageNumber: pageNumber.toString(), isArchived: false }, useConversationsInfiniteQuery(
{ enabled: isAuthenticated }, {
); 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>({ const { containerRef, moveToTop } = useNavScrolling<ConversationListResponse>({
setShowLoading, setShowLoading,
hasNextPage: searchQuery ? searchQueryRes.hasNextPage : hasNextPage, hasNextPage: searchQuery ? searchQueryRes.hasNextPage : hasNextPage,
@ -154,6 +164,7 @@ const Nav = ({ navVisible, setNavVisible }) => {
/> />
)} )}
</div> </div>
<BookmarkNav tags={tags} setTags={setTags} />
<NavLinks /> <NavLinks />
</nav> </nav>
</div> </div>
@ -168,7 +179,7 @@ const Nav = ({ navVisible, setNavVisible }) => {
navVisible={navVisible} navVisible={navVisible}
className="fixed left-0 top-1/2 z-40 hidden md:flex" 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> </Tooltip>
</TooltipProvider> </TooltipProvider>
); );

View 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;

View 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;

View 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;

View file

@ -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' ? 'dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white'
: '', : '',
)} )}
onClick={() => { onClick={(e) => {
if (link.onClick) { if (link.onClick) {
link.onClick(); link.onClick(e);
setActive(''); setActive('');
return; 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', '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', 'w-full justify-start rounded-md border dark:border-gray-700',
)} )}
onClick={() => { onClick={(e) => {
if (link.onClick) { if (link.onClick) {
link.onClick(); link.onClick(e);
setActive(''); setActive('');
} }
}} }}

View file

@ -12,6 +12,7 @@ import { ResizableHandleAlt, ResizablePanel, ResizablePanelGroup } from '~/compo
import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip'; import { TooltipProvider, Tooltip } from '~/components/ui/Tooltip';
import useSideNavLinks from '~/hooks/Nav/useSideNavLinks'; import useSideNavLinks from '~/hooks/Nav/useSideNavLinks';
import { useMediaQuery, useLocalStorage } from '~/hooks'; import { useMediaQuery, useLocalStorage } from '~/hooks';
import BookmarkPanel from './Bookmarks/BookmarkPanel';
import NavToggle from '~/components/Nav/NavToggle'; import NavToggle from '~/components/Nav/NavToggle';
import { useChatContext } from '~/Providers'; import { useChatContext } from '~/Providers';
import Switcher from './Switcher'; import Switcher from './Switcher';
@ -79,8 +80,20 @@ const SidePanel = ({
localStorage.setItem('fullPanelCollapse', 'true'); localStorage.setItem('fullPanelCollapse', 'true');
panelRef.current?.collapse(); 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 // eslint-disable-next-line react-hooks/exhaustive-deps
const throttledSaveLayout = useCallback( const throttledSaveLayout = useCallback(
@ -128,6 +141,7 @@ const SidePanel = ({
return ( return (
<> <>
{showBookmarks && <BookmarkPanel open={showBookmarks} onOpenChange={setShowBookmarks} />}
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
<ResizablePanelGroup <ResizablePanelGroup
direction="horizontal" direction="horizontal"
@ -216,7 +230,7 @@ const SidePanel = ({
</ResizablePanelGroup> </ResizablePanelGroup>
</TooltipProvider> </TooltipProvider>
<div <div
className={`nav-mask${!isCollapsed ? ' active' : ''}`} className={`nav-mask${!isCollapsed ? 'active' : ''}`}
onClick={() => { onClick={() => {
setIsCollapsed(() => { setIsCollapsed(() => {
localStorage.setItem('fullPanelCollapse', 'true'); localStorage.setItem('fullPanelCollapse', 'true');

View 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>
);
}

View file

@ -3,12 +3,21 @@ import {
LocalStorageKeys, LocalStorageKeys,
InfiniteCollections, InfiniteCollections,
defaultAssistantsVersion, defaultAssistantsVersion,
ConversationListResponse,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQueryClient } from '@tanstack/react-query';
import { dataService, MutationKeys, QueryKeys, defaultOrderQuery } from 'librechat-data-provider'; 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 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 { import {
/* Shared Links */ /* Shared Links */
addSharedLink, addSharedLink,
@ -19,9 +28,6 @@ import {
updateConversation, updateConversation,
deleteConversation, deleteConversation,
} from '~/utils'; } from '~/utils';
import { useConversationsInfiniteQuery, useSharedLinksInfiniteQuery } from './queries';
import { normalizeData } from '~/utils/collection';
import store from '~/store';
export type TGenTitleMutation = UseMutationResult< export type TGenTitleMutation = UseMutationResult<
t.TGenTitleResponse, 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 = ( export const useArchiveConversationMutation = (
id: string, id: string,
): UseMutationResult< ): 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 = ( export const useDeleteConversationMutation = (
options?: t.DeleteConversationOptions, options?: t.DeleteConversationOptions,
): UseMutationResult< ): UseMutationResult<

View file

@ -150,6 +150,7 @@ export const useConversationsInfiniteQuery = (
...params, ...params,
pageNumber: pageParam?.toString(), pageNumber: pageParam?.toString(),
isArchived: params?.isArchived || false, isArchived: params?.isArchived || false,
tags: params?.tags || [],
}), }),
{ {
getNextPageParam: (lastPage) => { 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 * ASSISTANTS
*/ */

View file

@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { import {
ArrowRightToLine, ArrowRightToLine,
MessageSquareQuote, MessageSquareQuote,
Bookmark,
// Settings2, // Settings2,
} from 'lucide-react'; } from 'lucide-react';
import { import {
@ -25,12 +26,14 @@ export default function useSideNavLinks({
keyProvided, keyProvided,
endpoint, endpoint,
interfaceConfig, interfaceConfig,
manageBookmarks,
}: { }: {
hidePanel: () => void; hidePanel: () => void;
assistants?: TConfig | null; assistants?: TConfig | null;
keyProvided: boolean; keyProvided: boolean;
endpoint?: EModelEndpoint | null; endpoint?: EModelEndpoint | null;
interfaceConfig: Partial<TInterfaceConfig>; interfaceConfig: Partial<TInterfaceConfig>;
manageBookmarks: (e?: React.MouseEvent) => void;
}) { }) {
const hasAccessToPrompts = useHasAccess({ const hasAccessToPrompts = useHasAccess({
permissionType: PermissionTypes.PROMPTS, permissionType: PermissionTypes.PROMPTS,
@ -73,6 +76,14 @@ export default function useSideNavLinks({
Component: FilesPanel, Component: FilesPanel,
}); });
links.push({
title: 'com_sidepanel_conversation_tags',
label: '',
icon: Bookmark,
onClick: manageBookmarks,
id: 'bookmarks',
});
links.push({ links.push({
title: 'com_sidepanel_hide_panel', title: 'com_sidepanel_hide_panel',
label: '', label: '',
@ -89,6 +100,7 @@ export default function useSideNavLinks({
endpoint, endpoint,
interfaceConfig.parameters, interfaceConfig.parameters,
hasAccessToPrompts, hasAccessToPrompts,
manageBookmarks,
]); ]);
return Links; return Links;

View file

@ -74,6 +74,20 @@ export default {
com_ui_unarchive: 'إلغاء الأرشفة', com_ui_unarchive: 'إلغاء الأرشفة',
com_ui_unarchive_error: 'فشل في إلغاء الأرشفة', com_ui_unarchive_error: 'فشل في إلغاء الأرشفة',
com_ui_more_options: 'المزيد', 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:
'تعذر تسجيل الدخول باستخدام المعلومات المقدمة. يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى.', 'تعذر تسجيل الدخول باستخدام المعلومات المقدمة. يرجى التحقق من بيانات الاعتماد الخاصة بك والمحاولة مرة أخرى.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -297,6 +311,8 @@ export default {
com_nav_help_faq: 'مساعدة & الأسئلة الشائعة', com_nav_help_faq: 'مساعدة & الأسئلة الشائعة',
com_nav_settings: 'الإعدادات', com_nav_settings: 'الإعدادات',
com_nav_search_placeholder: 'بحث في الرسائل', com_nav_search_placeholder: 'بحث في الرسائل',
com_nav_info_bookmarks_rebuild:
'إذا كان عدد الإشارات المرجعية غير صحيح، يرجى إعادة بناء معلومات الإشارة المرجعية. سيتم إعادة حساب عدد الإشارات المرجعية وستتم استعادة البيانات إلى حالتها الصحيحة.',
com_nav_setting_general: 'عام', com_nav_setting_general: 'عام',
com_nav_setting_data: 'تحكم في البيانات', com_nav_setting_data: 'تحكم في البيانات',
/* The following are AI translated */ /* The following are AI translated */
@ -346,6 +362,7 @@ export default {
com_sidepanel_hide_panel: 'إخفاء اللوحة', com_sidepanel_hide_panel: 'إخفاء اللوحة',
com_sidepanel_attach_files: 'إرفاق الملفات', com_sidepanel_attach_files: 'إرفاق الملفات',
com_sidepanel_manage_files: 'إدارة الملفات', com_sidepanel_manage_files: 'إدارة الملفات',
com_sidepanel_conversation_tags: 'الإشارات المرجعية',
com_assistants_capabilities: 'قدرات', com_assistants_capabilities: 'قدرات',
com_assistants_knowledge: 'المعرفة', com_assistants_knowledge: 'المعرفة',
com_assistants_completed_function: 'تم تشغيل {0}', com_assistants_completed_function: 'تم تشغيل {0}',
@ -856,6 +873,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'المزيد', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1652,6 +1725,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'بحث في الرسائل', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'عام', translated: 'عام',
@ -1832,6 +1911,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'إدارة الملفات', translated: 'إدارة الملفات',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'الإشارات المرجعية',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'قدرات', translated: 'قدرات',

View file

@ -11,6 +11,7 @@ export default {
com_sidepanel_hide_panel: 'Ocultar Painel', com_sidepanel_hide_panel: 'Ocultar Painel',
com_sidepanel_attach_files: 'Anexar Arquivos', com_sidepanel_attach_files: 'Anexar Arquivos',
com_sidepanel_manage_files: 'Gerenciar Arquivos', com_sidepanel_manage_files: 'Gerenciar Arquivos',
com_sidepanel_conversation_tags: 'Favoritos',
com_assistants_capabilities: 'Capacidades', com_assistants_capabilities: 'Capacidades',
com_assistants_knowledge: 'Conhecimento', com_assistants_knowledge: 'Conhecimento',
com_assistants_knowledge_info: 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.', '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_privacy_policy: 'Política de privacidade',
com_ui_terms_of_service: 'Termos de serviço', 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: com_auth_error_login:
'Não foi possível fazer login com as informações fornecidas. Por favor, verifique suas credenciais e tente novamente.', '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: com_auth_error_login_rl:
@ -467,6 +482,8 @@ export default {
com_nav_help_faq: 'Ajuda & FAQ', com_nav_help_faq: 'Ajuda & FAQ',
com_nav_settings: 'Configurações', com_nav_settings: 'Configurações',
com_nav_search_placeholder: 'Pesquisar mensagens', 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_general: 'Geral',
com_nav_setting_beta: 'Recursos beta', com_nav_setting_beta: 'Recursos beta',
com_nav_setting_data: 'Controles de dados', com_nav_setting_data: 'Controles de dados',
@ -509,6 +526,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'Gerenciar Arquivos', translated: 'Gerenciar Arquivos',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Favoritos',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Capacidades', translated: 'Capacidades',
@ -1103,6 +1124,62 @@ export const comparisons = {
english: 'Terms of service', english: 'Terms of service',
translated: 'Termos de serviço', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2160,6 +2237,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Pesquisar mensagens', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Geral', translated: 'Geral',

View file

@ -12,6 +12,7 @@ export default {
com_sidepanel_hide_panel: 'Seitenleiste ausblenden', com_sidepanel_hide_panel: 'Seitenleiste ausblenden',
com_sidepanel_attach_files: 'Dateien anhängen', com_sidepanel_attach_files: 'Dateien anhängen',
com_sidepanel_manage_files: 'Dateien verwalten', com_sidepanel_manage_files: 'Dateien verwalten',
com_sidepanel_conversation_tags: 'Lesezeichen',
com_assistants_capabilities: 'Fähigkeiten', com_assistants_capabilities: 'Fähigkeiten',
com_assistants_knowledge: 'Wissen', com_assistants_knowledge: 'Wissen',
com_assistants_knowledge_info: 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.', '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_privacy_policy: 'Datenschutzrichtlinie',
com_ui_terms_of_service: 'Nutzungsbedingungen', 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: com_auth_error_login:
'Die Anmeldung mit den angegebenen Daten ist fehlgeschlagen. Bitte überprüfe deine Anmeldedaten und versuche es erneut.', 'Die Anmeldung mit den angegebenen Daten ist fehlgeschlagen. Bitte überprüfe deine Anmeldedaten und versuche es erneut.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -480,6 +495,8 @@ export default {
com_nav_help_faq: 'Hilfe & FAQ', com_nav_help_faq: 'Hilfe & FAQ',
com_nav_settings: 'Einstellungen', com_nav_settings: 'Einstellungen',
com_nav_search_placeholder: 'Nachrichten suchen', 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_general: 'Allgemein',
com_nav_setting_beta: 'Beta-Funktionen', com_nav_setting_beta: 'Beta-Funktionen',
com_nav_setting_data: 'Datenkontrollen', com_nav_setting_data: 'Datenkontrollen',
@ -634,6 +651,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'Dateien verwalten', translated: 'Dateien verwalten',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Lesezeichen',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Fähigkeiten', translated: 'Fähigkeiten',
@ -1253,6 +1274,90 @@ export const comparisons = {
english: 'Terms of service', english: 'Terms of service',
translated: 'Nutzungsbedingungen', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2317,6 +2422,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Nachrichten suchen', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Allgemein', translated: 'Allgemein',

View file

@ -19,6 +19,7 @@ export default {
com_sidepanel_hide_panel: 'Hide Panel', com_sidepanel_hide_panel: 'Hide Panel',
com_sidepanel_attach_files: 'Attach Files', com_sidepanel_attach_files: 'Attach Files',
com_sidepanel_manage_files: 'Manage Files', com_sidepanel_manage_files: 'Manage Files',
com_sidepanel_conversation_tags: 'Bookmarks',
com_assistants_capabilities: 'Capabilities', com_assistants_capabilities: 'Capabilities',
com_assistants_file_search: 'File Search', com_assistants_file_search: 'File Search',
com_assistants_file_search_info: com_assistants_file_search_info:
@ -292,6 +293,20 @@ export default {
com_ui_use_micrphone: 'Use microphone', com_ui_use_micrphone: 'Use microphone',
com_ui_min_tags: 'Cannot remove more values, a minimum of {0} are required.', 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_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: com_auth_error_login:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
com_auth_error_login_rl: 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.', '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: 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.', '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_general: 'General',
com_nav_setting_chat: 'Chat', com_nav_setting_chat: 'Chat',
com_nav_setting_beta: 'Beta features', com_nav_setting_beta: 'Beta features',

View file

@ -11,6 +11,7 @@ export default {
com_sidepanel_hide_panel: 'Ocultar Panel', com_sidepanel_hide_panel: 'Ocultar Panel',
com_sidepanel_attach_files: 'Adjuntar Archivos', com_sidepanel_attach_files: 'Adjuntar Archivos',
com_sidepanel_manage_files: 'Administrar Archivos', com_sidepanel_manage_files: 'Administrar Archivos',
com_sidepanel_conversation_tags: 'Marcadores',
com_assistants_capabilities: 'Capacidades', com_assistants_capabilities: 'Capacidades',
com_assistants_knowledge: 'Conocimiento', com_assistants_knowledge: 'Conocimiento',
com_assistants_knowledge_info: 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.', '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_privacy_policy: 'Política de privacidad',
com_ui_terms_of_service: 'Términos de servicio', 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: com_auth_error_login:
'No se puede iniciar sesión con la información proporcionada. Verifique sus credenciales y vuelva a intentarlo.', 'No se puede iniciar sesión con la información proporcionada. Verifique sus credenciales y vuelva a intentarlo.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -473,6 +488,8 @@ export default {
com_nav_help_faq: 'Ayuda y preguntas frecuentes', com_nav_help_faq: 'Ayuda y preguntas frecuentes',
com_nav_settings: 'Configuración', com_nav_settings: 'Configuración',
com_nav_search_placeholder: 'Buscar mensajes', 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_general: 'General',
com_nav_setting_beta: 'Funciones beta', com_nav_setting_beta: 'Funciones beta',
com_nav_setting_data: 'Controles de datos', com_nav_setting_data: 'Controles de datos',
@ -631,6 +648,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'Administrar Archivos', translated: 'Administrar Archivos',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Marcadores',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Capacidades', translated: 'Capacidades',
@ -1226,6 +1247,62 @@ export const comparisons = {
english: 'Terms of service', english: 'Terms of service',
translated: 'Términos de servicio', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2292,6 +2369,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Buscar mensajes', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'General', translated: 'General',

View file

@ -292,6 +292,21 @@ export default {
com_ui_use_micrphone: 'Käytä mikrofonia', com_ui_use_micrphone: 'Käytä mikrofonia',
com_ui_min_tags: 'Enempää arvoja ei voida poistaa. Niiden minimimäärä on {0}.', 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_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: com_auth_error_login:
'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.', 'Kirjautuminen annetuilla tiedoilla ei onnistunut. Tarkista kirjautumistiedot, ja yritä uudestaan.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -415,7 +430,7 @@ export default {
com_endpoint_stop: 'Pysäytyssekvenssit', com_endpoint_stop: 'Pysäytyssekvenssit',
com_endpoint_stop_placeholder: 'Erota arvot toisistaan rivinvaihdoilla', 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ää. 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ä.`, Syötteen ja vastauksen kokonaispituutta rajoittaa mallin konteksti-ikkuna. Konteksti -ikkunan koon ylittämisestä voi seurata virheitä.`,
com_endpoint_openai_temp: com_endpoint_openai_temp:
'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.', 'Korkeampi arvo = satunnaisempi; matalampi arvo = keskittyneempi ja deterministisempi. Suosittelemme, että muokkaat tätä tai Top P:tä, mutta ei molempia.',

View file

@ -90,6 +90,20 @@ export default {
com_ui_preview: 'Aperçu', com_ui_preview: 'Aperçu',
com_ui_upload: 'Téléverser', com_ui_upload: 'Téléverser',
com_ui_connect: 'Connecter', 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: com_auth_error_login:
'Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.', 'Impossible de se connecter avec les informations fournies. Veuillez vérifier vos identifiants et réessayer.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -363,6 +377,8 @@ export default {
com_nav_help_faq: 'Aide & FAQ', com_nav_help_faq: 'Aide & FAQ',
com_nav_settings: 'Paramètres', com_nav_settings: 'Paramètres',
com_nav_search_placeholder: 'Rechercher des messages', 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_general: 'Général',
com_nav_setting_beta: 'Fonctionnalités bêta', com_nav_setting_beta: 'Fonctionnalités bêta',
com_nav_setting_data: 'Contrôles des données', com_nav_setting_data: 'Contrôles des données',
@ -449,6 +465,7 @@ export default {
com_sidepanel_hide_panel: 'Masquer le panneau', com_sidepanel_hide_panel: 'Masquer le panneau',
com_sidepanel_attach_files: 'Joindre des fichiers', com_sidepanel_attach_files: 'Joindre des fichiers',
com_sidepanel_manage_files: 'Gérer les fichiers', com_sidepanel_manage_files: 'Gérer les fichiers',
com_sidepanel_conversation_tags: 'Signets',
com_assistants_capabilities: 'Capacités des assistants', com_assistants_capabilities: 'Capacités des assistants',
com_assistants_knowledge: 'Connaissances', com_assistants_knowledge: 'Connaissances',
com_assistants_knowledge_info: com_assistants_knowledge_info:
@ -1030,6 +1047,62 @@ export const comparisons = {
english: 'Connect', english: 'Connect',
translated: 'Connecter', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1985,6 +2058,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Rechercher des 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Général', translated: 'Général',
@ -2306,6 +2385,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'Gérer les fichiers', translated: 'Gérer les fichiers',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Signets',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Capacités des assistants', translated: 'Capacités des assistants',

View file

@ -5,6 +5,7 @@ export default {
com_sidepanel_select_assistant: 'בחר סייען', com_sidepanel_select_assistant: 'בחר סייען',
com_sidepanel_assistant_builder: 'בניית סייען', com_sidepanel_assistant_builder: 'בניית סייען',
com_sidepanel_attach_files: 'צרף קבצים', com_sidepanel_attach_files: 'צרף קבצים',
com_sidepanel_conversation_tags: 'סימניות',
com_assistants_knowledge: 'ידע', com_assistants_knowledge: 'ידע',
com_assistants_knowledge_info: com_assistants_knowledge_info:
'אם אתה מעלה קבצים תחת ידע, שיחות עם ה-סייען שלך עשויות לכלול תוכן מהקובץ.', 'אם אתה מעלה קבצים תחת ידע, שיחות עם ה-סייען שלך עשויות לכלול תוכן מהקובץ.',
@ -119,6 +120,20 @@ export default {
_ui_preview: 'תצוגה מקדימה', _ui_preview: 'תצוגה מקדימה',
com_ui_upload: 'העלה', com_ui_upload: 'העלה',
com_ui_connect: 'התחבר', 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: 'לא ניתן להתחבר עם המידע שסופק. אנא בדוק את האישורים שלך ונסה שוב.',
com_auth_error_login_rl: 'יותר מדי ניסיונות כניסה בזמן קצר. בבקשה נסה שוב מאוחר יותר.', com_auth_error_login_rl: 'יותר מדי ניסיונות כניסה בזמן קצר. בבקשה נסה שוב מאוחר יותר.',
com_auth_error_login_ban: 'החשבון שלך נחסם באופן זמני עקב הפרות של השירות שלנו.', com_auth_error_login_ban: 'החשבון שלך נחסם באופן זמני עקב הפרות של השירות שלנו.',
@ -392,6 +407,8 @@ export default {
com_nav_help_faq: 'עזרה ושאלות נפוצות', com_nav_help_faq: 'עזרה ושאלות נפוצות',
com_nav_settings: 'הגדרות', com_nav_settings: 'הגדרות',
com_nav_search_placeholder: 'חפש הודעות', com_nav_search_placeholder: 'חפש הודעות',
com_nav_info_bookmarks_rebuild:
'אם ספירת הסימניות שגויה, נא לבנות מחדש את המידע של הסמניות. הספירה תחושב מחדש והנתונים ישוחזרו למצב הנכון.',
com_nav_setting_general: 'כללי', com_nav_setting_general: 'כללי',
com_nav_setting_beta: 'תכונות ביטא', com_nav_setting_beta: 'תכונות ביטא',
com_nav_setting_data: 'בקרות נתונים', com_nav_setting_data: 'בקרות נתונים',
@ -411,6 +428,10 @@ export const comparisons = {
english: 'Attach Files', english: 'Attach Files',
translated: 'צרף קבצים', translated: 'צרף קבצים',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'סימניות',
},
com_assistants_knowledge: { com_assistants_knowledge: {
english: 'Knowledge', english: 'Knowledge',
translated: 'ידע', translated: 'ידע',
@ -842,6 +863,62 @@ export const comparisons = {
english: 'Connect', english: 'Connect',
translated: 'התחבר', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1858,6 +1935,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'חפש הודעות', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'כללי', translated: 'כללי',

View file

@ -88,6 +88,20 @@ export default {
com_ui_preview: 'Pratinjau', com_ui_preview: 'Pratinjau',
com_ui_upload: 'Unggah', com_ui_upload: 'Unggah',
com_ui_connect: 'Hubungkan', 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: com_auth_error_login:
'Tidak dapat masuk dengan informasi yang diberikan. Silakan periksa kredensial Anda dan coba lagi.', 'Tidak dapat masuk dengan informasi yang diberikan. Silakan periksa kredensial Anda dan coba lagi.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -351,6 +365,8 @@ export default {
com_nav_help_faq: 'Bantuan & FAQ', com_nav_help_faq: 'Bantuan & FAQ',
com_nav_settings: 'Pengaturan', com_nav_settings: 'Pengaturan',
com_nav_search_placeholder: 'Cari pesan', 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_general: 'Umum',
com_nav_setting_beta: 'Fitur beta', com_nav_setting_beta: 'Fitur beta',
com_nav_setting_data: 'Kontrol data', com_nav_setting_data: 'Kontrol data',
@ -703,6 +719,62 @@ export const comparisons = {
english: 'Connect', english: 'Connect',
translated: 'Hubungkan', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1660,6 +1732,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Cari pesan', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Umum', translated: 'Umum',

View file

@ -19,6 +19,7 @@ export default {
com_sidepanel_hide_panel: 'Nascondi Pannello', com_sidepanel_hide_panel: 'Nascondi Pannello',
com_sidepanel_attach_files: 'Allega File', com_sidepanel_attach_files: 'Allega File',
com_sidepanel_manage_files: 'Gestisci File', com_sidepanel_manage_files: 'Gestisci File',
com_sidepanel_conversation_tags: 'Segnalibri',
com_assistants_capabilities: 'Capacità', com_assistants_capabilities: 'Capacità',
com_assistants_knowledge: 'Conoscenza', com_assistants_knowledge: 'Conoscenza',
com_assistants_knowledge_info: com_assistants_knowledge_info:
@ -224,6 +225,20 @@ export default {
com_ui_terms_of_service: 'Termini di servizio', com_ui_terms_of_service: 'Termini di servizio',
com_ui_min_tags: 'Impossibile rimuovere altri valori, è richiesto un minimo di {0}.', 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_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: com_auth_error_login:
'Impossibile eseguire l\'accesso con le informazioni fornite. Controlla le tue credenziali e riprova.', 'Impossibile eseguire l\'accesso con le informazioni fornite. Controlla le tue credenziali e riprova.',
com_auth_error_login_rl: 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_warning: 'ATTENZIONE: Questo cancellerà permanentemente il tuo account.',
com_dialog_delete_data_info: 'Tutti i tuoi dati verranno eliminati.', 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_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_general: 'Generale',
com_nav_setting_beta: 'Funzioni Beta', com_nav_setting_beta: 'Funzioni Beta',
com_nav_setting_data: 'Controlli dati', com_nav_setting_data: 'Controlli dati',
@ -662,6 +679,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'Gestisci File', translated: 'Gestisci File',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Segnalibri',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Capacità', translated: 'Capacità',
@ -1397,6 +1418,62 @@ export const comparisons = {
english: 'Maximum number allowed is {0}, using latest values.', english: 'Maximum number allowed is {0}, using latest values.',
translated: 'Il numero massimo consentito è {0}, verranno utilizzati gli ultimi valori.', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2471,6 +2548,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Cerca messaggi', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Generali', translated: 'Generali',

View file

@ -18,6 +18,7 @@ export default {
com_sidepanel_hide_panel: 'パネルを隠す', com_sidepanel_hide_panel: 'パネルを隠す',
com_sidepanel_attach_files: 'ファイルを添付する', com_sidepanel_attach_files: 'ファイルを添付する',
com_sidepanel_manage_files: 'ファイルを管理', com_sidepanel_manage_files: 'ファイルを管理',
com_sidepanel_conversation_tags: 'ブックマーク',
com_assistants_capabilities: 'Capabilities', com_assistants_capabilities: 'Capabilities',
com_assistants_knowledge: 'ナレッジ', com_assistants_knowledge: 'ナレッジ',
com_assistants_knowledge_info: com_assistants_knowledge_info:
@ -181,6 +182,20 @@ export default {
com_ui_terms_of_service: '利用規約', com_ui_terms_of_service: '利用規約',
com_ui_min_tags: 'これ以上の値を削除できません。少なくとも {0} が必要です。', com_ui_min_tags: 'これ以上の値を削除できません。少なくとも {0} が必要です。',
com_ui_max_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:
'入力された情報ではログインできませんでした。認証情報を確認した上で再度お試しください。', '入力された情報ではログインできませんでした。認証情報を確認した上で再度お試しください。',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -473,6 +488,8 @@ export default {
com_nav_help_faq: 'ヘルプ & FAQ', com_nav_help_faq: 'ヘルプ & FAQ',
com_nav_settings: '設定', com_nav_settings: '設定',
com_nav_search_placeholder: 'メッセージ検索', com_nav_search_placeholder: 'メッセージ検索',
com_nav_info_bookmarks_rebuild:
'ブックマークのカウントが正しくない場合は、ブックマークの情報を再構築してください。ブックマークのカウントが再計算され、データが正しい状態に復元されます。',
com_nav_setting_general: '一般', com_nav_setting_general: '一般',
com_nav_setting_beta: 'ベータ版の機能', com_nav_setting_beta: 'ベータ版の機能',
com_nav_setting_data: 'データ管理', com_nav_setting_data: 'データ管理',
@ -630,6 +647,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: 'ファイルを管理', translated: 'ファイルを管理',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'ブックマーク',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Capabilities', translated: 'Capabilities',
@ -1248,6 +1269,62 @@ export const comparisons = {
english: 'Maximum number allowed is {0}, using latest values.', english: 'Maximum number allowed is {0}, using latest values.',
translated: '最新の値を使用した場合、許可される最大数は {0} です。', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2303,6 +2380,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'メッセージ検索', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: '一般', translated: '一般',

View file

@ -74,6 +74,20 @@ export default {
com_ui_unarchive: '아카이브 해제', com_ui_unarchive: '아카이브 해제',
com_ui_unarchive_error: '대화 아카이브 해제 실패', com_ui_unarchive_error: '대화 아카이브 해제 실패',
com_ui_more_options: '더 보기', 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: '제공된 정보로 로그인할 수 없습니다. 자격 증명을 확인하고 다시 시도하세요.',
com_auth_no_account: '계정이 없으신가요?', com_auth_no_account: '계정이 없으신가요?',
com_auth_sign_up: '가입하기', com_auth_sign_up: '가입하기',
@ -279,6 +293,8 @@ export default {
com_nav_help_faq: '도움말 및 FAQ', com_nav_help_faq: '도움말 및 FAQ',
com_nav_settings: '설정', com_nav_settings: '설정',
com_nav_search_placeholder: '메시지 검색', com_nav_search_placeholder: '메시지 검색',
com_nav_info_bookmarks_rebuild:
'북마크 수가 정확하지 않은 경우 북마크 정보를 재구축하십시오. 북마크 수가 다시 계산되고 데이터가 올바른 상태로 복원됩니다.',
com_nav_setting_general: '일반', com_nav_setting_general: '일반',
com_nav_setting_data: '데이터 제어', com_nav_setting_data: '데이터 제어',
/* The following are AI Translated */ /* The following are AI Translated */
@ -328,6 +344,7 @@ export default {
com_sidepanel_hide_panel: '패널 숨기기', com_sidepanel_hide_panel: '패널 숨기기',
com_sidepanel_attach_files: '파일 첨부', com_sidepanel_attach_files: '파일 첨부',
com_sidepanel_manage_files: '파일 관리', com_sidepanel_manage_files: '파일 관리',
com_sidepanel_conversation_tags: '북마크',
com_assistants_capabilities: '기능', com_assistants_capabilities: '기능',
com_assistants_knowledge: '지식', com_assistants_knowledge: '지식',
com_assistants_knowledge_info: com_assistants_knowledge_info:
@ -854,6 +871,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: '더 보기', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1602,6 +1675,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: '메시지 검색', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: '일반', translated: '일반',
@ -1782,6 +1861,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: '파일 관리', translated: '파일 관리',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: '북마크',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: '기능', translated: '기능',

View file

@ -80,6 +80,24 @@ export default {
com_ui_unarchive: 'Uit archiveren', com_ui_unarchive: 'Uit archiveren',
com_ui_unarchive_error: 'Kan conversatie niet uit archiveren', com_ui_unarchive_error: 'Kan conversatie niet uit archiveren',
com_ui_more_options: 'Meer', 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: com_auth_error_login:
'Kan niet inloggen met de verstrekte informatie. Controleer uw referenties en probeer het opnieuw.', '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.', 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_help_faq: 'Help & FAQ',
com_nav_settings: 'Instellingen', com_nav_settings: 'Instellingen',
com_nav_search_placeholder: 'Berichten doorzoeken', 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_general: 'Algemeen',
com_nav_setting_data: 'Gegevensbesturing', com_nav_setting_data: 'Gegevensbesturing',
}; };
@ -610,6 +630,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'Meer', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1416,6 +1492,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Berichten doorzoeken', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Algemeen', translated: 'Algemeen',

View file

@ -53,6 +53,20 @@ export default {
com_ui_unarchive: 'Przywróć z archiwum', com_ui_unarchive: 'Przywróć z archiwum',
com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum', com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum',
com_ui_more_options: 'Więcej', 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: com_auth_error_login:
'Nie udało się zalogować przy użyciu podanych danych. Sprawdź swoje dane logowania i spróbuj ponownie.', '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?', 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_help_faq: 'Pomoc i często zadawane pytania',
com_nav_settings: 'Ustawienia', com_nav_settings: 'Ustawienia',
com_nav_search_placeholder: 'Szukaj wiadomości', 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_nav_setting_general: 'Ogólne',
com_ui_import_conversation: 'Importuj', com_ui_import_conversation: 'Importuj',
com_ui_import_conversation_info: 'Importuj konwersacje z pliku JSON', com_ui_import_conversation_info: 'Importuj konwersacje z pliku JSON',
@ -424,6 +440,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'Więcej', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1089,6 +1161,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Szukaj wiadomości', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Ogólne', translated: 'Ogólne',

View file

@ -90,6 +90,20 @@ export default {
com_ui_unarchive: 'разархивировать', com_ui_unarchive: 'разархивировать',
com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum', com_ui_unarchive_error: 'Nie udało się odtworzyć rozmowy z archiwum',
com_ui_more_options: 'Еще', 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:
'Не удалось войти с предоставленной информацией. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.', 'Не удалось войти с предоставленной информацией. Пожалуйста, проверьте ваши учетные данные и попробуйте снова.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -357,6 +371,8 @@ export default {
com_nav_help_faq: 'Помощь и Вопросы', com_nav_help_faq: 'Помощь и Вопросы',
com_nav_settings: 'Настройки', com_nav_settings: 'Настройки',
com_nav_search_placeholder: 'Поиск сообщений', com_nav_search_placeholder: 'Поиск сообщений',
com_nav_info_bookmarks_rebuild:
'Если количество закладок некорректно, пожалуйста, перестройте информацию о закладках. Количество закладок будет пересчитано, и данные будут восстановлены до правильного состояния.',
com_nav_setting_general: 'Общие', com_nav_setting_general: 'Общие',
com_nav_setting_beta: 'Бета-функции', com_nav_setting_beta: 'Бета-функции',
com_nav_setting_data: 'Управление данными', com_nav_setting_data: 'Управление данными',
@ -431,6 +447,7 @@ export default {
com_files_number_selected: 'Выбрано {0} из {1} файл(а/ов)', com_files_number_selected: 'Выбрано {0} из {1} файл(а/ов)',
com_sidepanel_parameters: 'Параметры', com_sidepanel_parameters: 'Параметры',
com_sidepanel_hide_panel: 'Скрыть панель', com_sidepanel_hide_panel: 'Скрыть панель',
com_sidepanel_conversation_tags: 'Закладки',
com_assistants_capabilities: 'Возможности', com_assistants_capabilities: 'Возможности',
com_assistants_image_vision: 'Анализ изображений', com_assistants_image_vision: 'Анализ изображений',
com_assistants_search_name: 'Поиск ассистентов по имени', com_assistants_search_name: 'Поиск ассистентов по имени',
@ -925,6 +942,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'Еще', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1876,6 +1949,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Поиск сообщений', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Общие', translated: 'Общие',
@ -2143,6 +2222,10 @@ export const comparisons = {
english: 'Hide Panel', english: 'Hide Panel',
translated: 'Скрыть панель', translated: 'Скрыть панель',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: 'Закладки',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: 'Возможности', translated: 'Возможности',

View file

@ -76,6 +76,20 @@ export default {
com_ui_unarchive: 'Avarkivera', com_ui_unarchive: 'Avarkivera',
com_ui_unarchive_error: 'Kunde inte avarkivera chatt', com_ui_unarchive_error: 'Kunde inte avarkivera chatt',
com_ui_more_options: 'Mer', 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: com_auth_error_login:
'Kunde inte logga in med den angivna informationen. Kontrollera dina uppgifter och försök igen.', 'Kunde inte logga in med den angivna informationen. Kontrollera dina uppgifter och försök igen.',
com_auth_error_login_rl: com_auth_error_login_rl:
@ -295,6 +309,8 @@ export default {
com_nav_help_faq: 'Hjälp & Vanliga frågor', com_nav_help_faq: 'Hjälp & Vanliga frågor',
com_nav_settings: 'Inställningar', com_nav_settings: 'Inställningar',
com_nav_search_placeholder: 'Sök meddelanden', 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_general: 'Allmänt',
com_nav_setting_data: 'Datakontroller', com_nav_setting_data: 'Datakontroller',
}; };
@ -589,6 +605,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'Mer', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1380,6 +1452,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Sök meddelanden', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Allmänt', translated: 'Allmänt',

View file

@ -250,6 +250,20 @@ export default {
com_ui_use_micrphone: 'Mikrofon kullan', com_ui_use_micrphone: 'Mikrofon kullan',
com_ui_min_tags: 'Daha fazla değer kaldırılamaz, en az {0} gereklidir.', 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_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: com_auth_error_login:
'Sağlanan bilgilerle giriş yapılamıyor. Lütfen kimlik bilgilerinizi kontrol edin ve tekrar deneyin.', 'Sağlanan bilgilerle giriş yapılamıyor. Lütfen kimlik bilgilerinizi kontrol edin ve tekrar deneyin.',
com_auth_error_login_rl: com_auth_error_login_rl:

View file

@ -78,6 +78,20 @@ export default {
com_ui_unarchive: 'Bỏ lưu trữ', 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_unarchive_error: 'Không thể bỏ lưu trữ cuộc trò chuyện',
com_ui_more_options: 'Thêm', 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: 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.', '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: 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_help_faq: 'Trợ giúp & Câu hỏi thường gặp',
com_nav_settings: 'Cài đặt', com_nav_settings: 'Cài đặt',
com_nav_search_placeholder: 'Tìm kiếm tin nhắn', 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_general: 'Chung',
com_nav_setting_data: 'Kiểm soát dữ liệu', com_nav_setting_data: 'Kiểm soát dữ liệu',
}; };
@ -588,6 +604,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: 'Thêm', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1361,6 +1433,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: 'Tìm kiếm tin nhắn', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: 'Chung', translated: 'Chung',

View file

@ -11,6 +11,7 @@ export default {
com_sidepanel_hide_panel: '隐藏侧边栏', com_sidepanel_hide_panel: '隐藏侧边栏',
com_sidepanel_attach_files: '附加文件', com_sidepanel_attach_files: '附加文件',
com_sidepanel_manage_files: '管理文件', com_sidepanel_manage_files: '管理文件',
com_sidepanel_conversation_tags: '书签',
com_assistants_capabilities: '功能', com_assistants_capabilities: '功能',
com_assistants_knowledge: '知识', com_assistants_knowledge: '知识',
com_assistants_knowledge_info: '如果您在“知识”中上传文件,与助手的对话可能包括文件内容。', com_assistants_knowledge_info: '如果您在“知识”中上传文件,与助手的对话可能包括文件内容。',
@ -159,6 +160,20 @@ export default {
com_ui_upload_delay: '上传 "{0}" 时比预期花了更长时间。 文件正在进行检索索引,请稍候。', com_ui_upload_delay: '上传 "{0}" 时比预期花了更长时间。 文件正在进行检索索引,请稍候。',
com_ui_privacy_policy: '隐私政策', com_ui_privacy_policy: '隐私政策',
com_ui_terms_of_service: '服务政策', 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: '无法登录,请确认提供的账户密码正确,并重新尝试。',
com_auth_error_login_rl: '尝试登录次数过多,请稍后再试。', com_auth_error_login_rl: '尝试登录次数过多,请稍后再试。',
com_auth_error_login_ban: '根据我们的服务规则,您的帐号被暂时禁用。', com_auth_error_login_ban: '根据我们的服务规则,您的帐号被暂时禁用。',
@ -431,6 +446,8 @@ export default {
com_nav_help_faq: '帮助', com_nav_help_faq: '帮助',
com_nav_settings: '设置', com_nav_settings: '设置',
com_nav_search_placeholder: '搜索对话及对话内容', com_nav_search_placeholder: '搜索对话及对话内容',
com_nav_info_bookmarks_rebuild:
'如果书签计数不正确,请重新构建书签信息。书签计数将被重新计算,数据将恢复到其正确状态。',
com_nav_setting_general: '通用', com_nav_setting_general: '通用',
com_nav_setting_beta: '实验特性', com_nav_setting_beta: '实验特性',
com_nav_setting_data: '数据管理', com_nav_setting_data: '数据管理',
@ -572,6 +589,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: '管理文件', translated: '管理文件',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: '书签',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: '功能', translated: '功能',
@ -1169,6 +1190,62 @@ export const comparisons = {
english: 'Terms of service', english: 'Terms of service',
translated: '服务政策', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -2209,6 +2286,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: '搜索对话及对话内容', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: '通用', translated: '通用',

View file

@ -71,6 +71,20 @@ export default {
com_ui_unarchive: '取消封存', com_ui_unarchive: '取消封存',
com_ui_unarchive_error: '取消封存對話時發生錯誤', com_ui_unarchive_error: '取消封存對話時發生錯誤',
com_ui_more_options: '更多', 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: '無法使用提供的資訊登入。請檢查您的登入資訊後重試。',
com_auth_error_login_rl: '短時間內嘗試登入的次數過多。請稍後再試。', com_auth_error_login_rl: '短時間內嘗試登入的次數過多。請稍後再試。',
com_auth_error_login_ban: '由於違反我們的服務條款,您的帳號已被暫時停用。', com_auth_error_login_ban: '由於違反我們的服務條款,您的帳號已被暫時停用。',
@ -281,6 +295,8 @@ export default {
com_nav_help_faq: '說明與常見問題', com_nav_help_faq: '說明與常見問題',
com_nav_settings: '設定', com_nav_settings: '設定',
com_nav_search_placeholder: '搜尋訊息', com_nav_search_placeholder: '搜尋訊息',
com_nav_info_bookmarks_rebuild:
'如果書籤計數不正確,請重新構建書籤信息。書籤計數將被重新計算,數據將恢復到其正確狀態。',
com_nav_setting_general: '一般', com_nav_setting_general: '一般',
com_nav_setting_data: '資料控制', com_nav_setting_data: '資料控制',
/* The following are AI translated */ /* The following are AI translated */
@ -328,6 +344,7 @@ export default {
com_sidepanel_hide_panel: '隱藏側邊選單', com_sidepanel_hide_panel: '隱藏側邊選單',
com_sidepanel_attach_files: '附加檔案', com_sidepanel_attach_files: '附加檔案',
com_sidepanel_manage_files: '管理檔案', com_sidepanel_manage_files: '管理檔案',
com_sidepanel_conversation_tags: '書籤',
com_assistants_capabilities: '功能', com_assistants_capabilities: '功能',
com_assistants_knowledge: '知識', com_assistants_knowledge: '知識',
com_assistants_knowledge_info: '如果您在「知識」下上傳檔案,與您的助理的對話可能會包含檔案內容。', com_assistants_knowledge_info: '如果您在「知識」下上傳檔案,與您的助理的對話可能會包含檔案內容。',
@ -830,6 +847,62 @@ export const comparisons = {
english: 'More', english: 'More',
translated: '更多', 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: { com_auth_error_login: {
english: english:
'Unable to login with the information provided. Please check your credentials and try again.', 'Unable to login with the information provided. Please check your credentials and try again.',
@ -1626,6 +1699,12 @@ export const comparisons = {
english: 'Search messages', english: 'Search messages',
translated: '搜尋訊息', 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: { com_nav_setting_general: {
english: 'General', english: 'General',
translated: '一般', translated: '一般',
@ -1806,6 +1885,10 @@ export const comparisons = {
english: 'Manage Files', english: 'Manage Files',
translated: '管理檔案', translated: '管理檔案',
}, },
com_sidepanel_conversation_tags: {
english: 'Bookmarks',
translated: '書籤',
},
com_assistants_capabilities: { com_assistants_capabilities: {
english: 'Capabilities', english: 'Capabilities',
translated: '功能', translated: '功能',

View 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);
});
});

View 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;
};

View file

@ -145,25 +145,37 @@ export const updateConversation = (
); );
}; };
export const updateConvoFields: ConversationUpdater = ( export const updateConvoFields = (
data: ConversationData, data: ConversationData,
updatedConversation: Partial<TConversation> & Pick<TConversation, 'conversationId'>, updatedConversation: Partial<TConversation> & Pick<TConversation, 'conversationId'>,
keepPosition = false,
): ConversationData => { ): ConversationData => {
const newData = JSON.parse(JSON.stringify(data)); const newData = JSON.parse(JSON.stringify(data));
const { pageIndex, index } = findPageForConversation( const { pageIndex, index } = findPageForConversation(
newData, newData,
updatedConversation as { conversationId: string }, updatedConversation as { conversationId: string },
); );
if (pageIndex !== -1 && index !== -1) { if (pageIndex !== -1 && index !== -1) {
const deleted = newData.pages[pageIndex].conversations.splice(index, 1); const oldConversation = newData.pages[pageIndex].conversations[index] as TConversation;
const oldConversation = deleted[0] as TConversation;
newData.pages[0].conversations.unshift({ /**
...oldConversation, * Do not change the position of the conversation if the tags are updated.
...updatedConversation, */
updatedAt: new Date().toISOString(), 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; return newData;

View file

@ -32,8 +32,10 @@ export const abortRequest = (endpoint: string) => `/api/ask/${endpoint}/abort`;
export const conversationsRoot = '/api/convos'; export const conversationsRoot = '/api/convos';
export const conversations = (pageNumber: string, isArchived?: boolean) => export const conversations = (pageNumber: string, isArchived?: boolean, tags?: string[]) =>
`${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}`; `${conversationsRoot}?pageNumber=${pageNumber}${isArchived ? '&isArchived=true' : ''}${tags
?.map((tag) => `&tags=${tag}`)
.join('')}`;
export const conversationById = (id: string) => `${conversationsRoot}/${id}`; 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 getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
export const updatePromptPermissions = (roleName: string) => export const updatePromptPermissions = (roleName: string) =>
`${roles()}/${roleName.toLowerCase()}/prompts`; `${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}`;

View file

@ -424,7 +424,8 @@ export const listConversations = (
// Assuming params has a pageNumber property // Assuming params has a pageNumber property
const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided const pageNumber = params?.pageNumber || '1'; // Default to page 1 if not provided
const isArchived = params?.isArchived || false; // Default to false 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 = ( export const listConversationsByQuery = (
@ -541,3 +542,34 @@ export function updatePromptPermissions(
): Promise<m.UpdatePromptPermResponse> { ): Promise<m.UpdatePromptPermResponse> {
return request.put(endpoints.updatePromptPermissions(variables.roleName), variables.updates); 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'));
}

View file

@ -36,6 +36,7 @@ export enum QueryKeys {
categories = 'categories', categories = 'categories',
randomPrompts = 'randomPrompts', randomPrompts = 'randomPrompts',
roles = 'roles', roles = 'roles',
conversationTags = 'conversationTags',
} }
export enum MutationKeys { export enum MutationKeys {

View file

@ -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 = ( export const useGetUserBalance = (
config?: UseQueryOptions<string>, config?: UseQueryOptions<string>,
): QueryObserverResult<string> => { ): QueryObserverResult<string> => {

View file

@ -371,6 +371,7 @@ export const tConversationSchema = z.object({
updatedAt: z.string(), updatedAt: z.string(),
modelLabel: z.string().nullable().optional(), modelLabel: z.string().nullable().optional(),
examples: z.array(tExampleSchema).optional(), examples: z.array(tExampleSchema).optional(),
tags: z.array(z.string()).optional(),
/* Prefer modelLabel over chatGptLabel */ /* Prefer modelLabel over chatGptLabel */
chatGptLabel: z.string().nullable().optional(), chatGptLabel: z.string().nullable().optional(),
userLabel: z.string().optional(), userLabel: z.string().optional(),
@ -476,6 +477,17 @@ export const tSharedLinkSchema = z.object({
}); });
export type TSharedLink = z.infer<typeof tSharedLinkSchema>; 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 export const openAISchema = tConversationSchema
.pick({ .pick({
model: true, model: true,

View file

@ -7,6 +7,7 @@ import type {
TSharedLink, TSharedLink,
TConversation, TConversation,
EModelEndpoint, EModelEndpoint,
TConversationTag,
} from './schemas'; } from './schemas';
import type { TSpecsConfig } from './models'; import type { TSpecsConfig } from './models';
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam; export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
@ -170,6 +171,25 @@ export type TSharedLinkResponse = TSharedLink;
export type TSharedLinksResponse = TSharedLink[]; export type TSharedLinksResponse = TSharedLink[];
export type TDeleteSharedLinkResponse = 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 = { export type TForkConvoRequest = {
messageId: string; messageId: string;
conversationId: string; conversationId: string;

View file

@ -173,3 +173,9 @@ export type UpdatePromptPermOptions = MutationOptions<
unknown, unknown,
types.TError types.TError
>; >;
export type UpdateConversationTagOptions = MutationOptions<
types.TConversationTag,
types.TConversationTagRequest
>;
export type DeleteConversationTagOptions = MutationOptions<types.TConversationTag, string>;

View file

@ -1,6 +1,7 @@
import type { InfiniteData } from '@tanstack/react-query'; import type { InfiniteData } from '@tanstack/react-query';
import type { TMessage, TConversation, TSharedLink } from '../schemas';
import type * as t from '../types'; import type * as t from '../types';
import type { TMessage, TConversation, TSharedLink, TConversationTag } from '../schemas';
export type Conversation = { export type Conversation = {
id: string; id: string;
createdAt: number; createdAt: number;
@ -18,6 +19,7 @@ export type ConversationListParams = {
pageNumber: string; // Add this line pageNumber: string; // Add this line
conversationId?: string; conversationId?: string;
isArchived?: boolean; isArchived?: boolean;
tags?: string[];
}; };
// Type for the response from the conversation list API // Type for the response from the conversation list API
@ -68,3 +70,5 @@ export type AllPromptGroupsFilterRequest = {
}; };
export type AllPromptGroupsResponse = t.TPromptGroup[]; export type AllPromptGroupsResponse = t.TPromptGroup[];
export type ConversationTagsResponse = TConversationTag[];