Compare commits
12 Commits
add-model-
...
v0.8.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1ad235f17 | ||
|
|
4a0b329e3e | ||
|
|
a22359de5e | ||
|
|
bbfe4002eb | ||
|
|
94426a3cae | ||
|
|
e559f0f4dc | ||
|
|
15c9c7e1f4 | ||
|
|
ac641e7cba | ||
|
|
1915d7b195 | ||
|
|
c2f4b383f2 | ||
|
|
939af59950 | ||
|
|
7d08da1a8a |
33
.github/workflows/i18n-unused-keys.yml
vendored
33
.github/workflows/i18n-unused-keys.yml
vendored
@@ -1,5 +1,10 @@
|
||||
name: Detect Unused i18next Strings
|
||||
|
||||
# This workflow checks for unused i18n keys in translation files.
|
||||
# It has special handling for:
|
||||
# - com_ui_special_var_* keys that are dynamically constructed
|
||||
# - com_agents_category_* keys that are stored in the database and used dynamically
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -7,6 +12,7 @@ on:
|
||||
- "api/**"
|
||||
- "packages/data-provider/src/**"
|
||||
- "packages/client/**"
|
||||
- "packages/data-schemas/src/**"
|
||||
|
||||
jobs:
|
||||
detect-unused-i18n-keys:
|
||||
@@ -24,7 +30,7 @@ jobs:
|
||||
|
||||
# Define paths
|
||||
I18N_FILE="client/src/locales/en/translation.json"
|
||||
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client")
|
||||
SOURCE_DIRS=("client/src" "api" "packages/data-provider/src" "packages/client" "packages/data-schemas/src")
|
||||
|
||||
# Check if translation file exists
|
||||
if [[ ! -f "$I18N_FILE" ]]; then
|
||||
@@ -52,6 +58,31 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
# Also check if the key is directly used somewhere
|
||||
if [[ "$FOUND" == false ]]; then
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
if grep -r --include=\*.{js,jsx,ts,tsx} -q "$KEY" "$DIR"; then
|
||||
FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
# Special case for agent category keys that are dynamically used from database
|
||||
elif [[ "$KEY" == com_agents_category_* ]]; then
|
||||
# Check if agent category localization is being used
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
# Check for dynamic category label/description usage
|
||||
if grep -r --include=\*.{js,jsx,ts,tsx} -E "category\.(label|description).*startsWith.*['\"]com_" "$DIR" > /dev/null 2>&1 || \
|
||||
# Check for the method that defines these keys
|
||||
grep -r --include=\*.{js,jsx,ts,tsx} "ensureDefaultCategories" "$DIR" > /dev/null 2>&1 || \
|
||||
# Check for direct usage in agentCategory.ts
|
||||
grep -r --include=\*.ts -E "label:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1 || \
|
||||
grep -r --include=\*.ts -E "description:.*['\"]$KEY['\"]" "$DIR" > /dev/null 2>&1; then
|
||||
FOUND=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Also check if the key is directly used somewhere
|
||||
if [[ "$FOUND" == false ]]; then
|
||||
for DIR in "${SOURCE_DIRS[@]}"; do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# v0.8.0-rc2
|
||||
# v0.8.0-rc3
|
||||
|
||||
# Base node image
|
||||
FROM node:20-alpine AS node
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Dockerfile.multi
|
||||
# v0.8.0-rc2
|
||||
# v0.8.0-rc3
|
||||
|
||||
# Base for all builds
|
||||
FROM node:20-alpine AS base-min
|
||||
|
||||
15
README.md
15
README.md
@@ -65,8 +65,10 @@
|
||||
|
||||
- 🔦 **Agents & Tools Integration**:
|
||||
- **[LibreChat Agents](https://www.librechat.ai/docs/features/agents)**:
|
||||
- No-Code Custom Assistants: Build specialized, AI-driven helpers without coding
|
||||
- Flexible & Extensible: Use MCP Servers, tools, file search, code execution, and more
|
||||
- No-Code Custom Assistants: Build specialized, AI-driven helpers
|
||||
- Agent Marketplace: Discover and deploy community-built agents
|
||||
- Collaborative Sharing: Share agents with specific users and groups
|
||||
- Flexible & Extensible: Use MCP Servers, tools, file search, code execution, and more
|
||||
- Compatible with Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, Google, Vertex AI, Responses API, and more
|
||||
- [Model Context Protocol (MCP) Support](https://modelcontextprotocol.io/clients#librechat) for Tools
|
||||
|
||||
@@ -87,15 +89,18 @@
|
||||
- Create, Save, & Share Custom Presets
|
||||
- Switch between AI Endpoints and Presets mid-chat
|
||||
- Edit, Resubmit, and Continue Messages with Conversation branching
|
||||
- Create and share prompts with specific users and groups
|
||||
- [Fork Messages & Conversations](https://www.librechat.ai/docs/features/fork) for Advanced Context control
|
||||
|
||||
- 💬 **Multimodal & File Interactions**:
|
||||
- Upload and analyze images with Claude 3, GPT-4.5, GPT-4o, o1, Llama-Vision, and Gemini 📸
|
||||
- Chat with Files using Custom Endpoints, OpenAI, Azure, Anthropic, AWS Bedrock, & Google 🗃️
|
||||
|
||||
- 🌎 **Multilingual UI**:
|
||||
- English, 中文, Deutsch, Español, Français, Italiano, Polski, Português Brasileiro
|
||||
- Русский, 日本語, Svenska, 한국어, Tiếng Việt, 繁體中文, العربية, Türkçe, Nederlands, עברית
|
||||
- 🌎 **Multilingual UI**:
|
||||
- English, 中文 (简体), 中文 (繁體), العربية, Deutsch, Español, Français, Italiano
|
||||
- Polski, Português (PT), Português (BR), Русский, 日本語, Svenska, 한국어, Tiếng Việt
|
||||
- Türkçe, Nederlands, עברית, Català, Čeština, Dansk, Eesti, فارسی
|
||||
- Suomi, Magyar, Հայերեն, Bahasa Indonesia, ქართული, Latviešu, ไทย, ئۇيغۇرچە
|
||||
|
||||
- 🧠 **Reasoning UI**:
|
||||
- Dynamic Reasoning UI for Chain-of-Thought/Reasoning AI models like DeepSeek-R1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/backend",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"start": "echo 'please run this from the root directory'",
|
||||
|
||||
@@ -46,7 +46,7 @@ router.use('/tools', tools);
|
||||
|
||||
/**
|
||||
* Get all agent categories with counts
|
||||
* @route GET /agents/marketplace/categories
|
||||
* @route GET /agents/categories
|
||||
*/
|
||||
router.get('/categories', v1.getAgentCategories);
|
||||
/**
|
||||
|
||||
@@ -149,7 +149,11 @@ const initializeClient = async ({ req, res, signal, endpointOption }) => {
|
||||
endpointOption,
|
||||
allowedProviders,
|
||||
});
|
||||
Object.assign(userMCPAuthMap, config.userMCPAuthMap ?? {});
|
||||
if (userMCPAuthMap != null) {
|
||||
Object.assign(userMCPAuthMap, config.userMCPAuthMap ?? {});
|
||||
} else {
|
||||
userMCPAuthMap = config.userMCPAuthMap;
|
||||
}
|
||||
agentConfigs.set(agentId, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const mongoose = require('mongoose');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const {
|
||||
logAgentMigrationWarning,
|
||||
@@ -16,7 +17,8 @@ const { findRoleByIdentifier } = require('~/models');
|
||||
async function checkMigrations() {
|
||||
try {
|
||||
const agentMigrationResult = await checkAgentPermissionsMigration({
|
||||
db: {
|
||||
mongoose,
|
||||
methods: {
|
||||
findRoleByIdentifier,
|
||||
getProjectByName,
|
||||
},
|
||||
@@ -28,7 +30,8 @@ async function checkMigrations() {
|
||||
}
|
||||
try {
|
||||
const promptMigrationResult = await checkPromptPermissionsMigration({
|
||||
db: {
|
||||
mongoose,
|
||||
methods: {
|
||||
findRoleByIdentifier,
|
||||
getProjectByName,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/frontend",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -225,7 +225,8 @@ export type AgentPanelContextType = {
|
||||
setActivePanel: React.Dispatch<React.SetStateAction<Panel>>;
|
||||
setCurrentAgentId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
agent_id?: string;
|
||||
agentsConfig?: t.TAgentsEndpoint;
|
||||
agentsConfig?: t.TAgentsEndpoint | null;
|
||||
endpointsConfig?: t.TEndpointsConfig | null;
|
||||
};
|
||||
|
||||
export type AgentModelPanelProps = {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { Label } from '@librechat/client';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { useLocalize, TranslationKeys, useAgentCategories } from '~/hooks';
|
||||
import { cn, renderAgentAvatar, getContactDisplayName } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
interface AgentCardProps {
|
||||
agent: t.Agent; // The agent data to display
|
||||
@@ -15,6 +15,21 @@ interface AgentCardProps {
|
||||
*/
|
||||
const AgentCard: React.FC<AgentCardProps> = ({ agent, onClick, className = '' }) => {
|
||||
const localize = useLocalize();
|
||||
const { categories } = useAgentCategories();
|
||||
|
||||
const categoryLabel = useMemo(() => {
|
||||
if (!agent.category) return '';
|
||||
|
||||
const category = categories.find((cat) => cat.value === agent.category);
|
||||
if (category) {
|
||||
if (category.label && category.label.startsWith('com_')) {
|
||||
return localize(category.label as TranslationKeys);
|
||||
}
|
||||
return category.label;
|
||||
}
|
||||
|
||||
return agent.category.charAt(0).toUpperCase() + agent.category.slice(1);
|
||||
}, [agent.category, categories, localize]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -49,9 +64,7 @@ const AgentCard: React.FC<AgentCardProps> = ({ agent, onClick, className = '' })
|
||||
{/* Category tag */}
|
||||
{agent.category && (
|
||||
<div className="inline-flex items-center rounded-md border-border-xheavy bg-surface-active-alt px-2 py-1 text-xs font-medium">
|
||||
<Label className="line-clamp-1 font-normal">
|
||||
{agent.category.charAt(0).toUpperCase() + agent.category.slice(1)}
|
||||
</Label>
|
||||
<Label className="line-clamp-1 font-normal">{categoryLabel}</Label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useAgentCategories } from '~/hooks/Agents';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
interface AgentCategoryDisplayProps {
|
||||
category?: string;
|
||||
className?: string;
|
||||
showIcon?: boolean;
|
||||
iconClassName?: string;
|
||||
showEmptyFallback?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component to display an agent category with proper translation
|
||||
*
|
||||
* @param category - The category value (e.g., "general", "hr", etc.)
|
||||
* @param className - Optional className for the container
|
||||
* @param showIcon - Whether to show the category icon
|
||||
* @param iconClassName - Optional className for the icon
|
||||
* @param showEmptyFallback - Whether to show a fallback for empty categories
|
||||
*/
|
||||
const AgentCategoryDisplay: React.FC<AgentCategoryDisplayProps> = ({
|
||||
category,
|
||||
className = '',
|
||||
showIcon = true,
|
||||
iconClassName = 'h-4 w-4 mr-2',
|
||||
showEmptyFallback = false,
|
||||
}) => {
|
||||
const { categories, emptyCategory } = useAgentCategories();
|
||||
|
||||
// Find the category in our processed categories list
|
||||
const categoryItem = categories.find((c) => c.value === category);
|
||||
|
||||
// Handle empty string case differently than undefined/null
|
||||
if (category === '') {
|
||||
if (!showEmptyFallback) {
|
||||
return null;
|
||||
}
|
||||
// Show the empty category placeholder
|
||||
return (
|
||||
<div className={cn('flex items-center text-gray-400', className)}>
|
||||
<span>{emptyCategory.label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// No category or unknown category
|
||||
if (!category || !categoryItem) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center', className)}>
|
||||
{showIcon && categoryItem.icon && (
|
||||
<span className={cn('flex-shrink-0', iconClassName)}>{categoryItem.icon}</span>
|
||||
)}
|
||||
<span>{categoryItem.label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgentCategoryDisplay;
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { useMediaQuery } from '@librechat/client';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { useLocalize, TranslationKeys } from '~/hooks';
|
||||
import { SmartLoader } from './SmartLoader';
|
||||
import { useLocalize } from '~/hooks/';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
/**
|
||||
@@ -36,14 +36,17 @@ const CategoryTabs: React.FC<CategoryTabsProps> = ({
|
||||
const localize = useLocalize();
|
||||
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
||||
|
||||
// Helper function to get category display name from database data
|
||||
/** Helper function to get category display name from database data */
|
||||
const getCategoryDisplayName = (category: t.TCategory) => {
|
||||
// Special cases for system categories
|
||||
if (category.value === 'promoted') {
|
||||
return localize('com_agents_top_picks');
|
||||
}
|
||||
if (category.value === 'all') {
|
||||
return 'All';
|
||||
return localize('com_agents_all_category');
|
||||
}
|
||||
if (category.label && category.label.startsWith('com_')) {
|
||||
return localize(category.label as TranslationKeys);
|
||||
}
|
||||
// Use database label or fallback to capitalized value
|
||||
return category.label || category.value.charAt(0).toUpperCase() + category.value.slice(1);
|
||||
@@ -158,7 +161,11 @@ const CategoryTabs: React.FC<CategoryTabsProps> = ({
|
||||
aria-selected={activeTab === category.value}
|
||||
aria-controls={`tabpanel-${category.value}`}
|
||||
tabIndex={activeTab === category.value ? 0 : -1}
|
||||
aria-label={`${getCategoryDisplayName(category)} tab (${index + 1} of ${categories.length})`}
|
||||
aria-label={localize('com_agents_category_tab_label', {
|
||||
category: getCategoryDisplayName(category),
|
||||
position: index + 1,
|
||||
total: categories.length,
|
||||
})}
|
||||
>
|
||||
{getCategoryDisplayName(category)}
|
||||
{/* Underline for active tab */}
|
||||
|
||||
@@ -7,8 +7,8 @@ import { TooltipAnchor, Button, NewChatIcon, useMediaQuery } from '@librechat/cl
|
||||
import { PermissionTypes, Permissions, QueryKeys, Constants } from 'librechat-data-provider';
|
||||
import type t from 'librechat-data-provider';
|
||||
import type { ContextType } from '~/common';
|
||||
import { useDocumentTitle, useHasAccess, useLocalize, TranslationKeys } from '~/hooks';
|
||||
import { useGetEndpointsQuery, useGetAgentCategoriesQuery } from '~/data-provider';
|
||||
import { useDocumentTitle, useHasAccess, useLocalize } from '~/hooks';
|
||||
import MarketplaceAdminSettings from './MarketplaceAdminSettings';
|
||||
import { SidePanelProvider, useChatContext } from '~/Providers';
|
||||
import { MarketplaceProvider } from './MarketplaceContext';
|
||||
@@ -381,8 +381,8 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||
}
|
||||
if (displayCategory === 'all') {
|
||||
return {
|
||||
name: 'All Agents',
|
||||
description: 'Browse all shared agents across all categories',
|
||||
name: localize('com_agents_all'),
|
||||
description: localize('com_agents_all_description'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -392,8 +392,12 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||
);
|
||||
if (categoryData) {
|
||||
return {
|
||||
name: categoryData.label,
|
||||
description: categoryData.description || '',
|
||||
name: categoryData.label?.startsWith('com_')
|
||||
? localize(categoryData.label as TranslationKeys)
|
||||
: categoryData.label,
|
||||
description: categoryData.description?.startsWith('com_')
|
||||
? localize(categoryData.description as TranslationKeys)
|
||||
: categoryData.description || '',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -455,8 +459,8 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||
}
|
||||
if (nextCategory === 'all') {
|
||||
return {
|
||||
name: 'All Agents',
|
||||
description: 'Browse all shared agents across all categories',
|
||||
name: localize('com_agents_all'),
|
||||
description: localize('com_agents_all_description'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -466,8 +470,16 @@ const AgentMarketplace: React.FC<AgentMarketplaceProps> = ({ className = '' }) =
|
||||
);
|
||||
if (categoryData) {
|
||||
return {
|
||||
name: categoryData.label,
|
||||
description: categoryData.description || '',
|
||||
name: categoryData.label?.startsWith('com_')
|
||||
? localize(categoryData.label as TranslationKeys)
|
||||
: categoryData.label,
|
||||
description: categoryData.description?.startsWith('com_')
|
||||
? localize(
|
||||
categoryData.description as Parameters<
|
||||
typeof localize
|
||||
>[0],
|
||||
)
|
||||
: categoryData.description || '',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ const mockLocalize = jest.fn((key: string, options?: any) => {
|
||||
com_agents_search_empty_heading: 'No search results',
|
||||
com_agents_created_by: 'by',
|
||||
com_agents_top_picks: 'Top Picks',
|
||||
com_agents_all_category: 'All',
|
||||
// ErrorDisplay translations
|
||||
com_agents_error_suggestion_generic: 'Try refreshing the page or check your network connection',
|
||||
com_agents_error_network_title: 'Network Error',
|
||||
@@ -199,7 +200,7 @@ describe('Accessibility Improvements', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const promotedTab = screen.getByRole('tab', { name: /Top Picks tab/ });
|
||||
const promotedTab = screen.getByRole('tab', { name: /Top Picks category/ });
|
||||
|
||||
// Test arrow key navigation
|
||||
fireEvent.keyDown(promotedTab, { key: 'ArrowRight' });
|
||||
@@ -226,8 +227,8 @@ describe('Accessibility Improvements', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const promotedTab = screen.getByRole('tab', { name: /Top Picks tab/ });
|
||||
const allTab = screen.getByRole('tab', { name: /All tab/ });
|
||||
const promotedTab = screen.getByRole('tab', { name: /Top Picks category/ });
|
||||
const allTab = screen.getByRole('tab', { name: /All category/ });
|
||||
|
||||
// Active tab should be focusable
|
||||
expect(promotedTab).toHaveAttribute('tabIndex', '0');
|
||||
|
||||
@@ -8,10 +8,42 @@ import type t from 'librechat-data-provider';
|
||||
jest.mock('~/hooks/useLocalize', () => () => (key: string) => {
|
||||
const mockTranslations: Record<string, string> = {
|
||||
com_agents_created_by: 'Created by',
|
||||
com_agents_agent_card_label: '{{name}} agent. {{description}}',
|
||||
com_agents_category_general: 'General',
|
||||
com_agents_category_hr: 'Human Resources',
|
||||
};
|
||||
return mockTranslations[key] || key;
|
||||
});
|
||||
|
||||
// Mock useAgentCategories hook
|
||||
jest.mock('~/hooks', () => ({
|
||||
useLocalize: () => (key: string, values?: Record<string, string>) => {
|
||||
const mockTranslations: Record<string, string> = {
|
||||
com_agents_created_by: 'Created by',
|
||||
com_agents_agent_card_label: '{{name}} agent. {{description}}',
|
||||
com_agents_category_general: 'General',
|
||||
com_agents_category_hr: 'Human Resources',
|
||||
};
|
||||
let translation = mockTranslations[key] || key;
|
||||
|
||||
// Replace placeholders with actual values
|
||||
if (values) {
|
||||
Object.entries(values).forEach(([placeholder, value]) => {
|
||||
translation = translation.replace(new RegExp(`{{${placeholder}}}`, 'g'), value);
|
||||
});
|
||||
}
|
||||
|
||||
return translation;
|
||||
},
|
||||
useAgentCategories: () => ({
|
||||
categories: [
|
||||
{ value: 'general', label: 'com_agents_category_general' },
|
||||
{ value: 'hr', label: 'com_agents_category_hr' },
|
||||
{ value: 'custom', label: 'Custom Category' }, // Non-localized custom category
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('AgentCard', () => {
|
||||
const mockAgent: t.Agent = {
|
||||
id: '1',
|
||||
@@ -200,6 +232,49 @@ describe('AgentCard', () => {
|
||||
|
||||
const card = screen.getByRole('button');
|
||||
expect(card).toHaveAttribute('tabIndex', '0');
|
||||
expect(card).toHaveAttribute('aria-label', 'com_agents_agent_card_label');
|
||||
expect(card).toHaveAttribute(
|
||||
'aria-label',
|
||||
'Test Agent agent. A test agent for testing purposes',
|
||||
);
|
||||
});
|
||||
|
||||
it('displays localized category label', () => {
|
||||
const agentWithCategory = {
|
||||
...mockAgent,
|
||||
category: 'general',
|
||||
};
|
||||
|
||||
render(<AgentCard agent={agentWithCategory} onClick={mockOnClick} />);
|
||||
|
||||
expect(screen.getByText('General')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays custom category label', () => {
|
||||
const agentWithCustomCategory = {
|
||||
...mockAgent,
|
||||
category: 'custom',
|
||||
};
|
||||
|
||||
render(<AgentCard agent={agentWithCustomCategory} onClick={mockOnClick} />);
|
||||
|
||||
expect(screen.getByText('Custom Category')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays capitalized fallback for unknown category', () => {
|
||||
const agentWithUnknownCategory = {
|
||||
...mockAgent,
|
||||
category: 'unknown',
|
||||
};
|
||||
|
||||
render(<AgentCard agent={agentWithUnknownCategory} onClick={mockOnClick} />);
|
||||
|
||||
expect(screen.getByText('Unknown')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not display category tag when category is not provided', () => {
|
||||
render(<AgentCard agent={mockAgent} onClick={mockOnClick} />);
|
||||
|
||||
expect(screen.queryByText('General')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Unknown')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import AgentCategoryDisplay from '../AgentCategoryDisplay';
|
||||
|
||||
// Mock the useAgentCategories hook
|
||||
jest.mock('~/hooks/Agents', () => ({
|
||||
useAgentCategories: () => ({
|
||||
categories: [
|
||||
{
|
||||
value: 'general',
|
||||
label: 'General',
|
||||
icon: <span data-testid="icon-general">{''}</span>,
|
||||
className: 'w-full',
|
||||
},
|
||||
{
|
||||
value: 'hr',
|
||||
label: 'HR',
|
||||
icon: <span data-testid="icon-hr">{''}</span>,
|
||||
className: 'w-full',
|
||||
},
|
||||
{
|
||||
value: 'rd',
|
||||
label: 'R&D',
|
||||
icon: <span data-testid="icon-rd">{''}</span>,
|
||||
className: 'w-full',
|
||||
},
|
||||
{
|
||||
value: 'finance',
|
||||
label: 'Finance',
|
||||
icon: <span data-testid="icon-finance">{''}</span>,
|
||||
className: 'w-full',
|
||||
},
|
||||
],
|
||||
emptyCategory: {
|
||||
value: '',
|
||||
label: 'General',
|
||||
className: 'w-full',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('AgentCategoryDisplay', () => {
|
||||
it('should display the proper label for a category', () => {
|
||||
render(<AgentCategoryDisplay category="rd" />);
|
||||
expect(screen.getByText('R&D')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display the icon when showIcon is true', () => {
|
||||
render(<AgentCategoryDisplay category="finance" showIcon={true} />);
|
||||
expect(screen.getByTestId('icon-finance')).toBeInTheDocument();
|
||||
expect(screen.getByText('Finance')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not display the icon when showIcon is false', () => {
|
||||
render(<AgentCategoryDisplay category="hr" showIcon={false} />);
|
||||
expect(screen.queryByTestId('icon-hr')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('HR')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply custom classnames', () => {
|
||||
render(<AgentCategoryDisplay category="general" className="test-class" />);
|
||||
expect(screen.getByText('General').parentElement).toHaveClass('test-class');
|
||||
});
|
||||
|
||||
it('should not render anything for unknown categories', () => {
|
||||
const { container } = render(<AgentCategoryDisplay category="unknown" />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should not render anything when no category is provided', () => {
|
||||
const { container } = render(<AgentCategoryDisplay />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should not render anything for empty category when showEmptyFallback is false', () => {
|
||||
const { container } = render(<AgentCategoryDisplay category="" />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it('should render empty category placeholder when showEmptyFallback is true', () => {
|
||||
render(<AgentCategoryDisplay category="" showEmptyFallback={true} />);
|
||||
expect(screen.getByText('General')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should apply custom iconClassName to the icon', () => {
|
||||
render(<AgentCategoryDisplay category="general" iconClassName="custom-icon-class" />);
|
||||
const iconElement = screen.getByTestId('icon-general').parentElement;
|
||||
expect(iconElement).toHaveClass('custom-icon-class');
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,8 @@ import type t from 'librechat-data-provider';
|
||||
jest.mock('~/hooks/useLocalize', () => () => (key: string) => {
|
||||
const mockTranslations: Record<string, string> = {
|
||||
com_agents_top_picks: 'Top Picks',
|
||||
com_agents_all: 'All',
|
||||
com_agents_all: 'All Agents',
|
||||
com_agents_all_category: 'All',
|
||||
com_ui_no_categories: 'No categories available',
|
||||
com_agents_category_tabs_label: 'Agent Categories',
|
||||
com_ui_agent_category_general: 'General',
|
||||
|
||||
@@ -89,7 +89,7 @@ const BookmarkEditDialog = ({
|
||||
<OGDialog open={open} onOpenChange={setOpen} triggerRef={triggerRef}>
|
||||
{children}
|
||||
<OGDialogTemplate
|
||||
title="Bookmark"
|
||||
title={bookmark ? localize('com_ui_bookmarks_edit') : localize('com_ui_bookmarks_new')}
|
||||
showCloseButton={false}
|
||||
className="w-11/12 md:max-w-2xl"
|
||||
main={
|
||||
|
||||
@@ -38,6 +38,8 @@ const BookmarkForm = ({
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm<TConversationTagRequest>({
|
||||
mode: 'onBlur',
|
||||
reValidateMode: 'onChange',
|
||||
defaultValues: {
|
||||
tag: bookmark?.tag ?? '',
|
||||
description: bookmark?.description ?? '',
|
||||
@@ -98,23 +100,30 @@ const BookmarkForm = ({
|
||||
<Input
|
||||
type="text"
|
||||
id="bookmark-tag"
|
||||
aria-label="Bookmark"
|
||||
aria-label={
|
||||
bookmark ? localize('com_ui_bookmarks_edit') : localize('com_ui_bookmarks_new')
|
||||
}
|
||||
{...register('tag', {
|
||||
required: 'tag is required',
|
||||
required: localize('com_ui_field_required'),
|
||||
maxLength: {
|
||||
value: 128,
|
||||
message: localize('com_auth_password_max_length'),
|
||||
message: localize('com_ui_field_max_length', {
|
||||
field: localize('com_ui_bookmarks_title'),
|
||||
length: 128,
|
||||
}),
|
||||
},
|
||||
validate: (value) => {
|
||||
return (
|
||||
value === bookmark?.tag ||
|
||||
bookmarks.every((bookmark) => bookmark.tag !== value) ||
|
||||
'tag must be unique'
|
||||
localize('com_ui_bookmarks_tag_exists')
|
||||
);
|
||||
},
|
||||
})}
|
||||
aria-invalid={!!errors.tag}
|
||||
placeholder="Bookmark"
|
||||
placeholder={
|
||||
bookmark ? localize('com_ui_bookmarks_edit') : localize('com_ui_bookmarks_new')
|
||||
}
|
||||
/>
|
||||
{errors.tag && <span className="text-sm text-red-500">{errors.tag.message}</span>}
|
||||
</div>
|
||||
@@ -127,7 +136,10 @@ const BookmarkForm = ({
|
||||
{...register('description', {
|
||||
maxLength: {
|
||||
value: 1048,
|
||||
message: 'Maximum 1048 characters',
|
||||
message: localize('com_ui_field_max_length', {
|
||||
field: localize('com_ui_bookmarks_description'),
|
||||
length: 1048,
|
||||
}),
|
||||
},
|
||||
})}
|
||||
id="bookmark-description"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { memo, useState } from 'react';
|
||||
import { UserIcon } from '@librechat/client';
|
||||
import { UserIcon, useAvatar } from '@librechat/client';
|
||||
import type { TUser } from 'librechat-data-provider';
|
||||
import type { IconProps } from '~/common';
|
||||
import MessageEndpointIcon from './MessageEndpointIcon';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useAvatar from '~/hooks/Messages/useAvatar';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
|
||||
@@ -2,11 +2,10 @@ import { useState, memo } from 'react';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import * as Select from '@ariakit/react/select';
|
||||
import { FileText, LogOut } from 'lucide-react';
|
||||
import { LinkIcon, GearIcon, DropdownMenuSeparator, UserIcon } from '@librechat/client';
|
||||
import { LinkIcon, GearIcon, DropdownMenuSeparator, Avatar } from '@librechat/client';
|
||||
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider';
|
||||
import FilesView from '~/components/Chat/Input/Files/FilesView';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
import useAvatar from '~/hooks/Messages/useAvatar';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import Settings from './Settings';
|
||||
import store from '~/store';
|
||||
@@ -21,9 +20,6 @@ function AccountSettings() {
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
|
||||
|
||||
const avatarSrc = useAvatar(user);
|
||||
const avatarSeed = user?.avatar || user?.name || user?.username || '';
|
||||
|
||||
return (
|
||||
<Select.SelectProvider>
|
||||
<Select.Select
|
||||
@@ -33,26 +29,7 @@ function AccountSettings() {
|
||||
>
|
||||
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
|
||||
<div className="relative flex">
|
||||
{avatarSeed.length === 0 ? (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgb(121, 137, 255)',
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
|
||||
}}
|
||||
className="relative flex items-center justify-center rounded-full p-1 text-text-primary"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<UserIcon />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
className="rounded-full"
|
||||
src={(user?.avatar ?? '') || avatarSrc}
|
||||
alt={`${user?.name || user?.username || user?.email || ''}'s avatar`}
|
||||
/>
|
||||
)}
|
||||
<Avatar user={user} size={32} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import { useLocalize } from '~/hooks';
|
||||
|
||||
export default function EmptyPromptPreview() {
|
||||
const localize = useLocalize();
|
||||
|
||||
return (
|
||||
<div className="h-full w-full content-center text-center font-bold dark:text-gray-200">
|
||||
Select or Create a Prompt
|
||||
<div className="h-full w-full content-center text-center font-bold text-text-secondary">
|
||||
{localize('com_ui_select_or_create_prompt')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ControlCombobox } from '@librechat/client';
|
||||
import {
|
||||
useWatch,
|
||||
@@ -9,7 +8,7 @@ import {
|
||||
useFormContext,
|
||||
ControllerRenderProps,
|
||||
} from 'react-hook-form';
|
||||
import { useAgentCategories } from '~/hooks/Agents';
|
||||
import { TranslationKeys, useLocalize, useAgentCategories } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
/**
|
||||
@@ -35,22 +34,25 @@ const useCategorySync = (agent_id: string | null) => {
|
||||
* A component for selecting agent categories with form validation
|
||||
*/
|
||||
const AgentCategorySelector: React.FC<{ className?: string }> = ({ className }) => {
|
||||
const { t } = useTranslation();
|
||||
const localize = useLocalize();
|
||||
const formContext = useFormContext();
|
||||
const { categories } = useAgentCategories();
|
||||
|
||||
// Always call useWatch
|
||||
const agent_id = useWatch({
|
||||
name: 'id',
|
||||
control: formContext.control,
|
||||
});
|
||||
|
||||
// Use custom hook for category sync
|
||||
const { syncCategory } = useCategorySync(agent_id);
|
||||
const getCategoryLabel = (category: { label: string; value: string }) => {
|
||||
if (category.label && category.label.startsWith('com_')) {
|
||||
return localize(category.label as TranslationKeys);
|
||||
}
|
||||
return category.label;
|
||||
};
|
||||
|
||||
// Transform categories to the format expected by ControlCombobox
|
||||
const comboboxItems = categories.map((category) => ({
|
||||
label: category.label,
|
||||
label: getCategoryLabel(category),
|
||||
value: category.value,
|
||||
}));
|
||||
|
||||
@@ -59,8 +61,8 @@ const AgentCategorySelector: React.FC<{ className?: string }> = ({ className })
|
||||
return categoryItem?.label || comboboxItems.find((c) => c.value === 'general')?.label;
|
||||
};
|
||||
|
||||
const searchPlaceholder = t('com_ui_search_agent_category', 'Search categories...');
|
||||
const ariaLabel = t('com_ui_agent_category_selector_aria', "Agent's category selector");
|
||||
const searchPlaceholder = localize('com_ui_search_agent_category');
|
||||
const ariaLabel = localize('com_ui_agent_category_selector_aria');
|
||||
|
||||
return (
|
||||
<Controller
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { EModelEndpoint, AgentCapabilities } from 'librechat-data-provider';
|
||||
import type { TAgentsEndpoint, TEndpointsConfig, TConfig } from 'librechat-data-provider';
|
||||
import type { TAgentsEndpoint, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
|
||||
interface UseGetAgentsConfigOptions {
|
||||
@@ -20,11 +20,12 @@ export default function useGetAgentsConfig(options?: UseGetAgentsConfigOptions):
|
||||
const endpointsConfig = providedConfig || queriedConfig;
|
||||
|
||||
const agentsConfig = useMemo<TAgentsEndpoint | null>(() => {
|
||||
const config = endpointsConfig?.[EModelEndpoint.agents] ?? null;
|
||||
const config: TAgentsEndpoint | null =
|
||||
(endpointsConfig?.[EModelEndpoint.agents] as TAgentsEndpoint | null) ?? null;
|
||||
if (!config) return null;
|
||||
|
||||
return {
|
||||
...(config as TConfig),
|
||||
...config,
|
||||
capabilities: Array.isArray(config.capabilities)
|
||||
? config.capabilities.map((cap) => cap as unknown as AgentCapabilities)
|
||||
: ([] as AgentCapabilities[]),
|
||||
|
||||
@@ -3,7 +3,6 @@ import { QueryKeys } from 'librechat-data-provider';
|
||||
import type { ConversationListResponse } from 'librechat-data-provider';
|
||||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type t from 'librechat-data-provider';
|
||||
import { updateConvoFieldsInfinite } from '~/utils/convos';
|
||||
|
||||
const useUpdateTagsInConvo = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -53,30 +52,31 @@ const useUpdateTagsInConvo = () => {
|
||||
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 &&
|
||||
'tags' in conversation &&
|
||||
Array.isArray((conversation as { tags?: string[] }).tags) &&
|
||||
(conversation as { tags?: string[] }).tags?.includes(tag)
|
||||
) {
|
||||
(conversation as { tags: string[] }).tags = (conversation as { tags: string[] }).tags.map(
|
||||
(t: string) => (t === tag ? newTag : t),
|
||||
);
|
||||
}
|
||||
return conversation;
|
||||
});
|
||||
if (data) {
|
||||
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 &&
|
||||
'tags' in conversation &&
|
||||
Array.isArray((conversation as { tags?: string[] }).tags) &&
|
||||
(conversation as { tags?: string[] }).tags?.includes(tag)
|
||||
) {
|
||||
(conversation as { tags: string[] }).tags = (
|
||||
conversation as { tags: string[] }
|
||||
).tags.map((t: string) => (t === tag ? newTag : t));
|
||||
}
|
||||
return conversation;
|
||||
});
|
||||
}
|
||||
queryClient.setQueryData<InfiniteData<ConversationListResponse>>(
|
||||
[QueryKeys.allConversations],
|
||||
newData,
|
||||
);
|
||||
}
|
||||
queryClient.setQueryData<InfiniteData<ConversationListResponse>>(
|
||||
[QueryKeys.allConversations],
|
||||
newData,
|
||||
);
|
||||
|
||||
const conversationIdsWithTag = [] as string[];
|
||||
|
||||
// update the tag to newTag from the cache of each conversation
|
||||
for (let i = 0; i < conversationIdsWithTag.length; i++) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as useAvatar } from './useAvatar';
|
||||
export { default as useProgress } from './useProgress';
|
||||
export { default as useAttachments } from './useAttachments';
|
||||
export { default as useSubmitMessage } from './useSubmitMessage';
|
||||
|
||||
@@ -480,12 +480,13 @@ export default function useEventHandlers({
|
||||
queryClient.setQueryData<TMessage[]>([QueryKeys.messages, id], _messages);
|
||||
};
|
||||
|
||||
/** Handle edge case where stream is cancelled before any response, which creates a blank page */
|
||||
if (
|
||||
!conversation.conversationId &&
|
||||
const hasNoResponse =
|
||||
responseMessage?.content?.[0]?.['text']?.value ===
|
||||
submission.initialResponse?.content?.[0]?.['text']?.value
|
||||
) {
|
||||
submission.initialResponse?.content?.[0]?.['text']?.value ||
|
||||
!!responseMessage?.content?.[0]?.['tool_call']?.auth;
|
||||
|
||||
/** Handle edge case where stream is cancelled before any response, which creates a blank page */
|
||||
if (!conversation.conversationId && hasNoResponse) {
|
||||
const currentConvoId =
|
||||
(submissionConvo.conversationId ?? conversation.conversationId) || Constants.NEW_CONVO;
|
||||
if (isNewConvo && submissionConvo.conversationId) {
|
||||
|
||||
@@ -6,8 +6,24 @@
|
||||
"com_a11y_start": "The AI has started their reply.",
|
||||
"com_agents_agent_card_label": "{{name}} agent. {{description}}",
|
||||
"com_agents_all": "All Agents",
|
||||
"com_agents_all_category": "All",
|
||||
"com_agents_all_description": "Browse all shared agents across all categories",
|
||||
"com_agents_by_librechat": "by LibreChat",
|
||||
"com_agents_category_aftersales": "After Sales",
|
||||
"com_agents_category_aftersales_description": "Agents specialized in post-sale support, maintenance, and customer service",
|
||||
"com_agents_category_empty": "No agents found in the {{category}} category",
|
||||
"com_agents_category_finance": "Finance",
|
||||
"com_agents_category_finance_description": "Agents specialized in financial analysis, budgeting, and accounting",
|
||||
"com_agents_category_general": "General",
|
||||
"com_agents_category_general_description": "General purpose agents for common tasks and inquiries",
|
||||
"com_agents_category_hr": "Human Resources",
|
||||
"com_agents_category_hr_description": "Agents specialized in HR processes, policies, and employee support",
|
||||
"com_agents_category_it": "IT",
|
||||
"com_agents_category_it_description": "Agents for IT support, technical troubleshooting, and system administration",
|
||||
"com_agents_category_rd": "Research & Development",
|
||||
"com_agents_category_rd_description": "Agents focused on R&D processes, innovation, and technical research",
|
||||
"com_agents_category_sales": "Sales",
|
||||
"com_agents_category_sales_description": "Agents focused on sales processes, customer relations",
|
||||
"com_agents_category_tab_label": "{{category}} category, {{position}} of {{total}}",
|
||||
"com_agents_category_tabs_label": "Agent Categories",
|
||||
"com_agents_clear_search": "Clear search",
|
||||
@@ -724,6 +740,7 @@
|
||||
"com_ui_bookmarks_edit": "Edit Bookmark",
|
||||
"com_ui_bookmarks_filter": "Filter bookmarks...",
|
||||
"com_ui_bookmarks_new": "New Bookmark",
|
||||
"com_ui_bookmarks_tag_exists": "A bookmark with this title already exists",
|
||||
"com_ui_bookmarks_title": "Title",
|
||||
"com_ui_bookmarks_update_error": "There was an error updating the bookmark",
|
||||
"com_ui_bookmarks_update_success": "Bookmark updated successfully",
|
||||
@@ -868,6 +885,7 @@
|
||||
"com_ui_feedback_tag_not_matched": "Didn't match my request",
|
||||
"com_ui_feedback_tag_other": "Other issue",
|
||||
"com_ui_feedback_tag_unjustified_refusal": "Refused without reason",
|
||||
"com_ui_field_max_length": "{{field}} must be less than {{length}} characters",
|
||||
"com_ui_field_required": "This field is required",
|
||||
"com_ui_file_size": "File Size",
|
||||
"com_ui_files": "Files",
|
||||
@@ -996,6 +1014,7 @@
|
||||
"com_ui_no_bookmarks": "it seems like you have no bookmarks yet. Click on a chat and add a new one",
|
||||
"com_ui_no_categories": "No categories available",
|
||||
"com_ui_no_category": "No category",
|
||||
"com_ui_no_changes": "No changes were made",
|
||||
"com_ui_no_data": "something needs to go here. was empty",
|
||||
"com_ui_no_individual_access": "No individual users or groups have access to this agent",
|
||||
"com_ui_no_personalization_available": "No personalization options are currently available",
|
||||
@@ -1111,6 +1130,7 @@
|
||||
"com_ui_select_file": "Select a file",
|
||||
"com_ui_select_model": "Select a model",
|
||||
"com_ui_select_options": "Select options...",
|
||||
"com_ui_select_or_create_prompt": "Select or Create a Prompt",
|
||||
"com_ui_select_provider": "Select a provider",
|
||||
"com_ui_select_provider_first": "Select a provider first",
|
||||
"com_ui_select_region": "Select a region",
|
||||
|
||||
@@ -4,31 +4,83 @@
|
||||
"com_a11y_ai_composing": "Mākslīgais intelekts joprojām veido.",
|
||||
"com_a11y_end": "Mākslīgais intelekts ir pabeidzis atbildi.",
|
||||
"com_a11y_start": "Mākslīgais intelekts ir sācis savu atbildi.",
|
||||
"com_agents_agent_card_label": "{{name}} aģents. {{description}}",
|
||||
"com_agents_all": "Visi aģenti",
|
||||
"com_agents_by_librechat": "no LibreChat",
|
||||
"com_agents_category_empty": "Nav atrasts neviens aģents {{category}} kategorijā",
|
||||
"com_agents_category_tab_label": "{{category}} kategorija, {{position}} no {{total}}",
|
||||
"com_agents_category_tabs_label": "Aģentu kategorijas",
|
||||
"com_agents_clear_search": "Notīrīt meklēšanu",
|
||||
"com_agents_code_interpreter": "Ja šī opcija ir iespējota, jūsu aģents var izmantot LibreChat koda interpretētāja API, lai droši palaistu ģenerēto kodu, tostarp failu apstrādi. Nepieciešama derīga API atslēga.",
|
||||
"com_agents_code_interpreter_title": "Koda interpretētāja API",
|
||||
"com_agents_contact": "Sazinieties ar",
|
||||
"com_agents_copy_link": "Kopēt saiti",
|
||||
"com_agents_create_error": "Izveidojot jūsu aģentu, radās kļūda.",
|
||||
"com_agents_created_by": "izveidojis",
|
||||
"com_agents_description_placeholder": "Pēc izvēles: aprakstiet savu aģentu šeit",
|
||||
"com_agents_empty_state_heading": "Nav atrasts neviens aģents",
|
||||
"com_agents_enable_file_search": "Iespējot failu meklēšanu",
|
||||
"com_agents_error_bad_request_message": "Pieprasījumu nevarēja apstrādāt.",
|
||||
"com_agents_error_bad_request_suggestion": "Lūdzu, pārbaudiet ievadītos datus un mēģiniet vēlreiz.",
|
||||
"com_agents_error_category_title": "Kategorija Kļūda",
|
||||
"com_agents_error_generic": "Satura ielādēšanas laikā radās problēma.",
|
||||
"com_agents_error_invalid_request": "Nepareizs pieprasījums",
|
||||
"com_agents_error_loading": "Kļūda aģentu ielādēšanā",
|
||||
"com_agents_error_network_message": "Nevar izveidot savienojumu ar serveri.",
|
||||
"com_agents_error_network_suggestion": "Pārbaudiet interneta savienojumu un mēģiniet vēlreiz.",
|
||||
"com_agents_error_network_title": "Savienojuma problēmas",
|
||||
"com_agents_error_not_found_message": "Pieprasīto saturu neizdevās atrast.",
|
||||
"com_agents_error_not_found_suggestion": "Mēģiniet pārlūkot citas iespējas vai atgriezieties katalogā.",
|
||||
"com_agents_error_not_found_title": "Nav atrasts",
|
||||
"com_agents_error_retry": "Mēģiniet vēlreiz",
|
||||
"com_agents_error_search_title": "Meklēšanas kļūda",
|
||||
"com_agents_error_searching": "Kļūda meklējot aģentus",
|
||||
"com_agents_error_server_message": "Serveris uz laiku nav pieejams.",
|
||||
"com_agents_error_server_suggestion": "Lūdzu, mēģiniet vēlreiz pēc dažām minūtēm.",
|
||||
"com_agents_error_server_title": "Servera kļūda",
|
||||
"com_agents_error_suggestion_generic": "Lūdzu, mēģiniet atsvaidzināt lapu vai mēģiniet vēlreiz vēlāk.",
|
||||
"com_agents_error_timeout_message": "Pieprasījuma izpildei bija nepieciešams pārāk ilgs laiks.",
|
||||
"com_agents_error_timeout_suggestion": "Lūdzu, pārbaudiet interneta savienojumu un mēģiniet vēlreiz.",
|
||||
"com_agents_error_timeout_title": "Savienojumu neizdevās izveidot",
|
||||
"com_agents_error_title": "Kaut kas nogāja greizi",
|
||||
"com_agents_file_context": "Failu konteksts (OCR)",
|
||||
"com_agents_file_context_disabled": "Pirms failu augšupielādes failu kontekstam ir jāizveido aģents.",
|
||||
"com_agents_file_context_info": "Faili, kas augšupielādēti kā “Konteksts”, tiek apstrādāti, izmantojot OCR, lai iegūtu tekstu, kas pēc tam tiek pievienots aģenta norādījumiem. Ideāli piemērots dokumentiem, attēliem ar tekstu vai PDF failiem, kuriem nepieciešams pilns faila teksta saturs.",
|
||||
"com_agents_file_search_disabled": "Pirms failu augšupielādes failu meklēšanai ir jāizveido aģents.",
|
||||
"com_agents_file_search_info": "Kad šī opcija ir iespējota, aģents tiks informēts par precīziem tālāk norādītajiem failu nosaukumiem, ļaujot tam izgūt atbilstošu kontekstu no šiem failiem.",
|
||||
"com_agents_grid_announcement": "Rādu {{count}} aģentus {{category}} kategorijā",
|
||||
"com_agents_instructions_placeholder": "Sistēmas instrukcijas, ko izmantos aģents",
|
||||
"com_agents_link_copied": "Saite nokopēta",
|
||||
"com_agents_link_copy_failed": "Neizdevās nokopēt saiti",
|
||||
"com_agents_load_more_label": "Ielādēt vairāk aģentus no {{category}} kategorijas",
|
||||
"com_agents_loading": "Notiek ielāde...",
|
||||
"com_agents_marketplace": "Aģentu katalogs",
|
||||
"com_agents_marketplace_subtitle": "Atklājiet un izmantojiet jaudīgus mākslīgā intelekta aģentus, lai uzlabotu darba plūsmu un produktivitāti.",
|
||||
"com_agents_mcp_description_placeholder": "Dažos vārdos paskaidrojiet, ko tas dara.",
|
||||
"com_agents_mcp_icon_size": "Minimālais izmērs 128 x 128 px",
|
||||
"com_agents_mcp_info": "MCP serveru pievienošana savam aģentam, lai tas varētu veikt uzdevumus un mijiedarboties ar ārējiem pakalpojumiem.",
|
||||
"com_agents_mcp_name_placeholder": "Pielāgotais rīks",
|
||||
"com_agents_mcp_trust_subtext": "LibreChat nav validējis neoficiālus savienotājus",
|
||||
"com_agents_mcps_disabled": "Pirms MCP pievienošanas ir jāizveido aģents.",
|
||||
"com_agents_missing_name": "Pirms aģenta izveidošanas ievadiet aģenta nosaukumu.",
|
||||
"com_agents_missing_provider_model": "Pirms aģenta izveides izvēlieties pakalpojumu sniedzēju un modeli.",
|
||||
"com_agents_name_placeholder": "Pēc izvēles: aģenta nosaukums",
|
||||
"com_agents_no_access": "Jums nav piekļuves šī aģenta rediģēšanai.",
|
||||
"com_agents_no_agent_id_error": "Nav atrasts aģenta ID. Lūdzu, pārliecinieties, ka aģents ir izveidots.",
|
||||
"com_agents_no_more_results": "Jūs esat sasniedzis rezultātu beigas",
|
||||
"com_agents_not_available": "Aģents nav pieejams",
|
||||
"com_agents_recommended": "Mūsu rekomendētie aģenti",
|
||||
"com_agents_results_for": "Rezultāti par '{{query}}'",
|
||||
"com_agents_search_aria": "Meklēt aģentus",
|
||||
"com_agents_search_empty_heading": "Nav meklēšanas rezultātu",
|
||||
"com_agents_search_info": "Ja šī opcija ir iespējota, jūsu aģents var meklēt jaunāko informāciju tīmeklī. Nepieciešama derīga API atslēga.",
|
||||
"com_agents_search_instructions": "Rakstiet lai meklētu aģentus pēc vārda vai apraksta",
|
||||
"com_agents_search_name": "Meklēt aģentus pēc nosaukuma",
|
||||
"com_agents_search_no_results": "Nav atrasts neviens aģents \"{{query}}\"",
|
||||
"com_agents_search_placeholder": "Meklēt aģentus...",
|
||||
"com_agents_see_more": "Skatīt vairāk",
|
||||
"com_agents_start_chat": "Sākt sarunu",
|
||||
"com_agents_top_picks": "Labākās izvēles",
|
||||
"com_agents_update_error": "Jūsu aģenta atjaunināšanā ir kļūda.",
|
||||
"com_assistants_action_attempt": "Asistents vēlas runāt ar {{0}}",
|
||||
"com_assistants_actions": "Darbības",
|
||||
@@ -308,10 +360,21 @@
|
||||
"com_error_moderation": "Šķiet, ka mūsu moderācijas sistēma ir atzīmējusi iesniegto saturu kā neatbilstošu mūsu vadlīnijām. Mēs nevaram turpināt darbu ar šo konkrēto tēmu. Ja jums ir vēl kādi jautājumi vai tēmas, kuras vēlaties izpētīt, lūdzu, rediģējiet savu ziņu vai izveidojiet jaunu sarunu.",
|
||||
"com_error_no_base_url": "Nav atrasts bāzes URL. Lūdzu, norādiet to un mēģiniet vēlreiz.",
|
||||
"com_error_no_user_key": "Atslēga nav atrasta. Lūdzu, norādiet atslēgu un mēģiniet vēlreiz.",
|
||||
"com_file_pages": "Lapas: {{pages}}",
|
||||
"com_file_source": "Fails",
|
||||
"com_file_unknown": "Nezināms fails",
|
||||
"com_files_download_failed": "{{0}} failu lejuplāde neizdevās",
|
||||
"com_files_download_percent_complete": "{{0}}% pabeigts",
|
||||
"com_files_download_progress": "{{0}} no {{1}} failiem",
|
||||
"com_files_downloading": "Notiek failu lejuplādēšana",
|
||||
"com_files_filter": "Filtrēt failus...",
|
||||
"com_files_no_results": "Nav rezultātu.",
|
||||
"com_files_number_selected": "{{0}} no {{1}} atlasīti faili",
|
||||
"com_files_preparing_download": "Sagatavošanās lejupielādei...",
|
||||
"com_files_sharepoint_picker_title": "Izvēlieties failus",
|
||||
"com_files_table": "kaut kam šeit ir jānotiek. bija tukšs",
|
||||
"com_files_upload_local_machine": "No lokālā datora",
|
||||
"com_files_upload_sharepoint": "No SharePoint",
|
||||
"com_generated_files": "Ģenerētie faili:",
|
||||
"com_hide_examples": "Slēpt piemērus",
|
||||
"com_info_heic_converting": "Konvertēju HEIC attēlu uz JPEG...",
|
||||
@@ -347,7 +410,7 @@
|
||||
"com_nav_balance_month": "mēnesis",
|
||||
"com_nav_balance_months": "mēneši",
|
||||
"com_nav_balance_next_refill": "Nākamā bilances papildināšana:",
|
||||
"com_nav_balance_next_refill_info": "Nākamā bilances papildināšana notiks automātiski tikai tad, ja būs izpildīti abi nosacījumi: kopš pēdējās bilances papildināšanas ir pagājis norādītais laika atjaunošanas biežums un uzaicinājuma nosūtīšana izraisītu jūsu atlikuma samazināšanos zem nulles.",
|
||||
"com_nav_balance_next_refill_info": "Nākamā bilances papildināšana notiks automātiski tikai tad, ja būs izpildīti abi nosacījumi: kopš pēdējās bilances papildināšanas ir pagājis norādītais laika atjaunošanas biežums un vaicājuma nosūtīšana izraisītu jūsu atlikuma samazināšanos zem nulles.",
|
||||
"com_nav_balance_refill_amount": "Bilances papildināšanas apjoms:",
|
||||
"com_nav_balance_second": "otrais",
|
||||
"com_nav_balance_seconds": "sekundes",
|
||||
@@ -415,6 +478,7 @@
|
||||
"com_nav_lang_arabic": "العربية",
|
||||
"com_nav_lang_armenian": "Հայերեն",
|
||||
"com_nav_lang_auto": "Automātiska noteikšana",
|
||||
"com_nav_lang_bosnian": "Босански",
|
||||
"com_nav_lang_brazilian_portuguese": "Brazīlijas portugāļu",
|
||||
"com_nav_lang_catalan": "Katalā",
|
||||
"com_nav_lang_chinese": "中文",
|
||||
@@ -434,6 +498,7 @@
|
||||
"com_nav_lang_japanese": "日本語",
|
||||
"com_nav_lang_korean": "한국어",
|
||||
"com_nav_lang_latvian": "Latviešu",
|
||||
"com_nav_lang_norwegian_bokmal": "Norsk Bokmål",
|
||||
"com_nav_lang_persian": "فارسی",
|
||||
"com_nav_lang_polish": "Poļu",
|
||||
"com_nav_lang_portuguese": "Portugāļu",
|
||||
@@ -515,9 +580,21 @@
|
||||
"com_sidepanel_manage_files": "Pārvaldīt failus",
|
||||
"com_sidepanel_mcp_no_servers_with_vars": "Nav MCP serveru ar konfigurējamiem mainīgajiem.",
|
||||
"com_sidepanel_parameters": "Parametri",
|
||||
"com_sources_agent_file": "Avota dokuments",
|
||||
"com_sources_agent_files": "Aģentu faili",
|
||||
"com_sources_download_aria_label": "Lejupielādēt {{filename}}{{status}}",
|
||||
"com_sources_download_failed": "Lejupielādē neizdevās",
|
||||
"com_sources_download_local_unavailable": "Neizdevās lejupielādēt: Faili nav saglabāti",
|
||||
"com_sources_downloading_status": " (lejupielādē...)",
|
||||
"com_sources_error_fallback": "Nevar ielādēt avotus",
|
||||
"com_sources_image_alt": "Meklēšanas rezultāta attēls",
|
||||
"com_sources_more_files": "+{{count}} faili",
|
||||
"com_sources_more_sources": "+{{count}} avoti",
|
||||
"com_sources_pages": "Lapas",
|
||||
"com_sources_region_label": "Meklēšanas rezultāti un avoti",
|
||||
"com_sources_reload_page": "Atkārtoti ielādēt lapu",
|
||||
"com_sources_tab_all": "Visi",
|
||||
"com_sources_tab_files": "Faili",
|
||||
"com_sources_tab_images": "Attēli",
|
||||
"com_sources_tab_news": "Ziņas",
|
||||
"com_sources_title": "Avoti",
|
||||
@@ -546,6 +623,14 @@
|
||||
"com_ui_advanced": "Advancēts",
|
||||
"com_ui_advanced_settings": "Advancētie iestatījumi",
|
||||
"com_ui_agent": "Aģents",
|
||||
"com_ui_agent_category_aftersales": "Pēcpārdošanas pakalpojumi",
|
||||
"com_ui_agent_category_finance": "Finanses",
|
||||
"com_ui_agent_category_general": "Vispārīgi",
|
||||
"com_ui_agent_category_hr": "Personāla daļa",
|
||||
"com_ui_agent_category_it": "IT",
|
||||
"com_ui_agent_category_rd": "Pētniecība un attīstība",
|
||||
"com_ui_agent_category_sales": "Pārdošana",
|
||||
"com_ui_agent_category_selector_aria": "Aģenta kategorijas atlasītājs",
|
||||
"com_ui_agent_chain": "Aģentu ķēde (aģentu maisījums)",
|
||||
"com_ui_agent_chain_info": "Ļauj izveidot aģentu secību ķēdes. Katrs aģents var piekļūt iepriekšējo ķēdē esošo aģentu izvades datiem. Balstīts uz \"Aģentu sajaukuma\" arhitektūru, kurā aģenti izmanto iepriekšējos izvades datus kā palīginformāciju.",
|
||||
"com_ui_agent_chain_max": "Jūs esat sasniedzis maksimālo skaitu {{0}} aģentu.",
|
||||
@@ -553,8 +638,10 @@
|
||||
"com_ui_agent_deleted": "Aģents veiksmīgi izdzēsts",
|
||||
"com_ui_agent_duplicate_error": "Dublējot aģentu, radās kļūda.",
|
||||
"com_ui_agent_duplicated": "Aģents veiksmīgi dublēts",
|
||||
"com_ui_agent_name_is_required": "Obligāti jānorāda aģenta nosaukums",
|
||||
"com_ui_agent_recursion_limit": "Maksimālais aģenta soļu skaits",
|
||||
"com_ui_agent_recursion_limit_info": "Ierobežo, cik soļus aģents var veikt vienā izpildes reizē, pirms sniedz galīgo atbildi. Noklusējuma vērtība ir 25 soļi. Solis ir vai nu AI API pieprasījums, vai rīka lietošanas kārta. Piemēram, pamata rīka mijiedarbība ietver 3 soļus: sākotnējo pieprasījumu, rīka lietošanu un turpmāko pieprasījumu.",
|
||||
"com_ui_agent_url_copied": "Aģenta URL kopēts starpliktuvē",
|
||||
"com_ui_agent_var": "{{0}} aģents",
|
||||
"com_ui_agent_version": "Versija",
|
||||
"com_ui_agent_version_active": "Aktīvā versija",
|
||||
@@ -571,6 +658,7 @@
|
||||
"com_ui_agent_version_unknown_date": "Nezināms datums",
|
||||
"com_ui_agents": "Aģenti",
|
||||
"com_ui_agents_allow_create": "Atļaut aģentu izveidi",
|
||||
"com_ui_agents_allow_share": "Atļaut aģentu koplietošanu",
|
||||
"com_ui_agents_allow_use": "Atļaut aģentu izmantošanu",
|
||||
"com_ui_all": "visi",
|
||||
"com_ui_all_proper": "Visi",
|
||||
@@ -591,6 +679,7 @@
|
||||
"com_ui_assistant_deleted": "Asistents ir veiksmīgi izdzēsts.",
|
||||
"com_ui_assistants": "Asistenti",
|
||||
"com_ui_assistants_output": "Asistentu izvade",
|
||||
"com_ui_at_least_one_owner_required": "Nepieciešams vismaz viens īpašnieks",
|
||||
"com_ui_attach_error": "Nevar pievienot failu. Izveidojiet vai atlasiet sarunu vai mēģiniet atsvaidzināt lapu.",
|
||||
"com_ui_attach_error_openai": "Nevar pievienot asistenta failus citiem galapunktiem",
|
||||
"com_ui_attach_error_size": "Galapunkta faila lieluma ierobežojums ir pārsniegts:",
|
||||
@@ -607,12 +696,16 @@
|
||||
"com_ui_available_tools": "Pieejamie rīki",
|
||||
"com_ui_avatar": "Avatars",
|
||||
"com_ui_azure": "Azure",
|
||||
"com_ui_azure_ad": "Azure Entra ID",
|
||||
"com_ui_back": "Atpakaļ",
|
||||
"com_ui_back_to_chat": "Atpakaļ uz sarunu",
|
||||
"com_ui_back_to_prompts": "Atpakaļ pie uzvednēm",
|
||||
"com_ui_backup_code_number": "Kods #{{number}}",
|
||||
"com_ui_backup_codes": "Rezerves kodi",
|
||||
"com_ui_backup_codes_regenerate_error": "Atkārtoti ģenerējot rezerves kodus, radās kļūda.",
|
||||
"com_ui_backup_codes_regenerated": "Rezerves kodi ir veiksmīgi atjaunoti",
|
||||
"com_ui_backup_codes_security_info": "Drošības apsvērumu dēļ rezerves kodi tiek parādīti tikai vienu reizi. Lūdzu, saglabājiet tos drošā vietā.",
|
||||
"com_ui_backup_codes_status": "Rezerves kodu statuss",
|
||||
"com_ui_basic": "Pamata",
|
||||
"com_ui_basic_auth_header": "Pamata autorizācijas galvene",
|
||||
"com_ui_bearer": "Nesējs",
|
||||
@@ -669,6 +762,7 @@
|
||||
"com_ui_copy_code": "Kopēt kodu",
|
||||
"com_ui_copy_link": "Kopēt saiti",
|
||||
"com_ui_copy_to_clipboard": "Kopēt starpliktuvē",
|
||||
"com_ui_copy_url_to_clipboard": "URL kopēšana uz starpliktuvi",
|
||||
"com_ui_create": "Izveidot",
|
||||
"com_ui_create_link": "Izveidot saiti",
|
||||
"com_ui_create_memory": "Izveidot atmiņu",
|
||||
@@ -754,6 +848,7 @@
|
||||
"com_ui_error_connection": "Kļūda, izveidojot savienojumu ar serveri, mēģiniet atsvaidzināt lapu.",
|
||||
"com_ui_error_save_admin_settings": "Saglabājot administratora iestatījumus, radās kļūda.",
|
||||
"com_ui_error_updating_preferences": "Kļūda, atjauninot preferences",
|
||||
"com_ui_everyone_permission_level": "Visu lietotāju atļaujas līmenis",
|
||||
"com_ui_examples": "Piemēri",
|
||||
"com_ui_expand_chat": "Izvērst sarunu",
|
||||
"com_ui_export_convo_modal": "Eksportēt sarunas modālo logu",
|
||||
@@ -818,6 +913,7 @@
|
||||
"com_ui_good_afternoon": "Labdien",
|
||||
"com_ui_good_evening": "Labvakar",
|
||||
"com_ui_good_morning": "Labrīt",
|
||||
"com_ui_group": "Grupa",
|
||||
"com_ui_happy_birthday": "Man šodien ir pirmā dzimšanas diena!",
|
||||
"com_ui_hide_image_details": "Slēpt attēla detaļas",
|
||||
"com_ui_hide_password": "Paslēpt paroli",
|
||||
@@ -851,6 +947,8 @@
|
||||
"com_ui_logo": "{{0}} Logotips",
|
||||
"com_ui_low": "Zems",
|
||||
"com_ui_manage": "Pārvaldīt",
|
||||
"com_ui_marketplace": "Katalogs",
|
||||
"com_ui_marketplace_allow_use": "Atļaut izmantot katalogu",
|
||||
"com_ui_max_tags": "Maksimālais atļautais skaits ir {{0}}, izmantojot jaunākās vērtības.",
|
||||
"com_ui_mcp_authenticated_success": "MCP serveris '{{0}}' veiksmīgi autentificēts",
|
||||
"com_ui_mcp_enter_var": "Ievadiet vērtību {{0}}",
|
||||
@@ -898,10 +996,13 @@
|
||||
"com_ui_next": "Nākamais",
|
||||
"com_ui_no": "Nē",
|
||||
"com_ui_no_bookmarks": "Šķiet, ka jums vēl nav grāmatzīmju. Noklikšķiniet uz sarunas un pievienojiet jaunu.",
|
||||
"com_ui_no_categories": "Nav pieejamas nevienas kategorijas",
|
||||
"com_ui_no_category": "Nav kategorijas",
|
||||
"com_ui_no_data": "kaut kam šeit ir jānotiek. bija tukšs",
|
||||
"com_ui_no_individual_access": "Aatsevišķiem lietotājiem vai grupām nav pieejas pie šī aģenta",
|
||||
"com_ui_no_personalization_available": "Pašlaik nav pieejamas personalizācijas opcijas",
|
||||
"com_ui_no_read_access": "Jums nav atļaujas skatīt atmiņas",
|
||||
"com_ui_no_results_found": "Nav atrastu rezultātu",
|
||||
"com_ui_no_terms_content": "Nav noteikumu un nosacījumu satura, ko parādīt",
|
||||
"com_ui_no_valid_items": "kaut kam šeit ir jānotiek. bija tukšs",
|
||||
"com_ui_none": "Neviens",
|
||||
@@ -924,6 +1025,14 @@
|
||||
"com_ui_openai": "OpenAI",
|
||||
"com_ui_optional": "(pēc izvēles)",
|
||||
"com_ui_page": "Lapa",
|
||||
"com_ui_people": "cilvēki",
|
||||
"com_ui_people_picker": "Cilvēku atlasītājs",
|
||||
"com_ui_people_picker_allow_view_groups": "Atļaut skatīt grupas",
|
||||
"com_ui_people_picker_allow_view_roles": "Atļaut lomu skatīšanu",
|
||||
"com_ui_people_picker_allow_view_users": "Atļaut skatīt lietotājus",
|
||||
"com_ui_permissions_failed_load": "Neizdevās ielādēt pieejas tiesības. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_ui_permissions_failed_update": "Neizdevās atjaunināt pieejas tiesības. Lūdzu, mēģiniet vēlreiz.",
|
||||
"com_ui_permissions_updated_success": "Pieejas tiesības ir veiksmīgi atjauninātas.",
|
||||
"com_ui_preferences_updated": "Preferences veiksmīgi atjauninātas",
|
||||
"com_ui_prev": "Iepriekšējais",
|
||||
"com_ui_preview": "Priekšskatījums",
|
||||
@@ -938,6 +1047,7 @@
|
||||
"com_ui_prompt_update_error": "Atjauninot uzvedni, radās kļūda.",
|
||||
"com_ui_prompts": "Uzvednes",
|
||||
"com_ui_prompts_allow_create": "Atļaut uzvedņu izveidi",
|
||||
"com_ui_prompts_allow_share": "Atļaut kopīgošanas uzvednes",
|
||||
"com_ui_prompts_allow_use": "Atļaut izmantot uzvednes",
|
||||
"com_ui_provider": "Pakalpojumu sniedzējs",
|
||||
"com_ui_quality": "Kvalitāte",
|
||||
@@ -945,12 +1055,14 @@
|
||||
"com_ui_redirecting_to_provider": "Pārvirzu uz {{0}}, lūdzu, uzgaidiet...",
|
||||
"com_ui_reference_saved_memories": "References uz saglabātajām atmiņām",
|
||||
"com_ui_reference_saved_memories_description": "Ļaut asistentam atsaukties uz jūsu saglabātajām atmiņām un izmantot tās atbildot",
|
||||
"com_ui_refresh": "Atsvaidzināt",
|
||||
"com_ui_refresh_link": "Atsvaidzināt saiti",
|
||||
"com_ui_regenerate": "Atjaunot",
|
||||
"com_ui_regenerate_backup": "Atjaunot rezerves kodus",
|
||||
"com_ui_regenerating": "Atjaunojas...",
|
||||
"com_ui_region": "Reģions",
|
||||
"com_ui_reinitialize": "Reinicializēt",
|
||||
"com_ui_remove_user": "Noņemt {{0}}",
|
||||
"com_ui_rename": "Pārdēvēt",
|
||||
"com_ui_rename_conversation": "Pārdēvēt sarunu",
|
||||
"com_ui_rename_failed": "Neizdevās pārdēvēt sarunu",
|
||||
@@ -958,6 +1070,7 @@
|
||||
"com_ui_requires_auth": "Nepieciešama autentifikācija",
|
||||
"com_ui_reset_var": "Atiestatīt {{0}}",
|
||||
"com_ui_reset_zoom": "Atiestatīt tālummaiņu",
|
||||
"com_ui_resource": "resurss",
|
||||
"com_ui_result": "Rezultāts",
|
||||
"com_ui_revoke": "Atsaukt",
|
||||
"com_ui_revoke_info": "Atcelt visus lietotāja sniegtos lietotāja datus",
|
||||
@@ -965,24 +1078,41 @@
|
||||
"com_ui_revoke_key_endpoint": "Atsaukt atslēgu priekš {{0}}",
|
||||
"com_ui_revoke_keys": "Atsaukt atslēgas",
|
||||
"com_ui_revoke_keys_confirm": "Vai tiešām vēlaties atsaukt visas atslēgas?",
|
||||
"com_ui_role": "Loma",
|
||||
"com_ui_role_editor": "Redaktors",
|
||||
"com_ui_role_editor_desc": "Var skatīt un rediģēt aģentu",
|
||||
"com_ui_role_manager": "Vadītājs",
|
||||
"com_ui_role_manager_desc": "Var skatīt, rediģēt un dzēst aģentu",
|
||||
"com_ui_role_owner": "Īpašnieks",
|
||||
"com_ui_role_owner_desc": "Pilnīga kontrole pār aģentu, tostarp tā koplietošana",
|
||||
"com_ui_role_select": "Loma",
|
||||
"com_ui_role_viewer": "Skatītājs",
|
||||
"com_ui_role_viewer_desc": "Var skatīt un izmantot aģentu, bet nevar to rediģēt",
|
||||
"com_ui_roleplay": "Lomu spēle",
|
||||
"com_ui_run_code": "Palaišanas kods",
|
||||
"com_ui_run_code_error": "Radās kļūda, izpildot kodu",
|
||||
"com_ui_save": "Saglabāt",
|
||||
"com_ui_save_badge_changes": "Vai saglabāt emblēmas izmaiņas?",
|
||||
"com_ui_save_changes": "Saglabāt izmaiņas",
|
||||
"com_ui_save_submit": "Saglabāt un iesniegt",
|
||||
"com_ui_saved": "Saglabāts!",
|
||||
"com_ui_saving": "Saglabā...",
|
||||
"com_ui_schema": "Shēma",
|
||||
"com_ui_scope": "Mērogs",
|
||||
"com_ui_search": "Meklēt",
|
||||
"com_ui_search_above_to_add": "Meklēt augstāk, lai pievienotu lietotājus vai grupas",
|
||||
"com_ui_search_above_to_add_all": "Meklēt augstāk, lai pievienotu lietotājus, grupas vai lomas",
|
||||
"com_ui_search_above_to_add_people": "Meklēt augstāk, lai pievienotu personas",
|
||||
"com_ui_search_agent_category": "Meklēt kategorijas...",
|
||||
"com_ui_search_default_placeholder": "Meklēt pēc vārda vai e-pasta adreses (vismaz 2 rakstu zīmes)",
|
||||
"com_ui_search_people_placeholder": "Meklēt personas vai grupas pēc vārda vai e-pasta adreses",
|
||||
"com_ui_seconds": "sekundes",
|
||||
"com_ui_secret_key": "Slepenā atslēga",
|
||||
"com_ui_select": "Atlasīt",
|
||||
"com_ui_select_all": "Atlasīt visu",
|
||||
"com_ui_select_file": "Atlasiet failu",
|
||||
"com_ui_select_model": "Izvēlieties modeli",
|
||||
"com_ui_select_options": "Izvēlieties opcijas...",
|
||||
"com_ui_select_provider": "Izvēlieties pakalpojumu sniedzēju",
|
||||
"com_ui_select_provider_first": "Vispirms izvēlieties pakalpojumu sniedzēju",
|
||||
"com_ui_select_region": "Izvēlieties reģionu",
|
||||
@@ -995,6 +1125,8 @@
|
||||
"com_ui_share_create_message": "Jūsu vārds un visas ziņas, ko pievienojat pēc kopīgošanas, paliek privātas.",
|
||||
"com_ui_share_delete_error": "Dzēšot koplietoto saiti, radās kļūda.",
|
||||
"com_ui_share_error": "Kopīgojot sarunas saiti, radās kļūda.",
|
||||
"com_ui_share_everyone": "Koplietot ar visiem",
|
||||
"com_ui_share_everyone_description_var": "Šis {{resource}} būs pieejams ikvienam. Lūdzu, pārliecinieties, ka {{resource}} patiesībā ir paredzēts koplietošanai ar visiem. Esiet uzmanīgi ar saviem datiem.",
|
||||
"com_ui_share_link_to_chat": "Kopīgot saiti sarunai",
|
||||
"com_ui_share_update_message": "Jūsu vārds, pielāgotie norādījumi un visas ziņas, ko pievienojat pēc kopīgošanas, paliek privātas.",
|
||||
"com_ui_share_var": "Kopīgot {{0}}",
|
||||
@@ -1021,6 +1153,13 @@
|
||||
"com_ui_stop": "Apstāties",
|
||||
"com_ui_storage": "Uzglabāšana",
|
||||
"com_ui_submit": "Iesniegt",
|
||||
"com_ui_support_contact": "Atbalsta kontaktinformācija",
|
||||
"com_ui_support_contact_email": "E-pasts",
|
||||
"com_ui_support_contact_email_invalid": "Lūdzu, ievadiet derīgu e-pasta adresi",
|
||||
"com_ui_support_contact_email_placeholder": "atbalsts@piemers.com",
|
||||
"com_ui_support_contact_name": "Vārds",
|
||||
"com_ui_support_contact_name_min_length": "Vārdam jābūt vismaz {{minLength}} rakstu zīmēm",
|
||||
"com_ui_support_contact_name_placeholder": "Atbalsta kontaktpersonas vārds",
|
||||
"com_ui_teach_or_explain": "Mācīšanās",
|
||||
"com_ui_temporary": "Pagaidu saruna",
|
||||
"com_ui_terms_and_conditions": "Noteikumi un nosacījumi",
|
||||
@@ -1037,6 +1176,7 @@
|
||||
"com_ui_tools": "Rīki",
|
||||
"com_ui_travel": "Ceļošana",
|
||||
"com_ui_trust_app": "Es uzticos šai lietotnei",
|
||||
"com_ui_try_adjusting_search": "Mēģiniet pielāgot meklēšanas vaicājumus",
|
||||
"com_ui_unarchive": "Atarhivēt",
|
||||
"com_ui_unarchive_error": "Neizdevās atarhivēt sarunu",
|
||||
"com_ui_unknown": "Nezināms",
|
||||
@@ -1046,6 +1186,7 @@
|
||||
"com_ui_update_mcp_error": "Izveidojot vai atjauninot MCP, radās kļūda.",
|
||||
"com_ui_update_mcp_success": "Veiksmīgi izveidots vai atjaunināts MCP",
|
||||
"com_ui_upload": "Augšupielādēt",
|
||||
"com_ui_upload_agent_avatar": "Aģenta avatars veiksmīgi atjaunināts",
|
||||
"com_ui_upload_code_files": "Augšupielādēt koda interpretētājam",
|
||||
"com_ui_upload_delay": "Augšupielāde \"{{0}}\" aizņem vairāk laika nekā paredzēts. Lūdzu, uzgaidiet, kamēr faila indeksēšana ir pabeigta izguvei.",
|
||||
"com_ui_upload_error": "Augšupielādējot failu, radās kļūda.",
|
||||
@@ -1065,6 +1206,8 @@
|
||||
"com_ui_use_memory": "Izmantot atmiņu",
|
||||
"com_ui_use_micrphone": "Izmantot mikrofonu",
|
||||
"com_ui_used": "Lietots",
|
||||
"com_ui_user": "Lietotājs",
|
||||
"com_ui_user_group_permissions": "Lietotāju un grupu atļaujas",
|
||||
"com_ui_value": "Vērtība",
|
||||
"com_ui_variables": "Mainīgie",
|
||||
"com_ui_variables_info": "Mainīgo veidošanai tekstā izmantot dubultās iekavas, piemēram, `{{example variable}}`, lai vēlāk aizpildītu, izmantojot uzvedni.",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"com_a11y_ai_composing": "De AI is nog bezig met het formuleren van een antwoord.",
|
||||
"com_a11y_end": "De AI is klaar met het antwoord.",
|
||||
"com_a11y_start": "De AI is begonnen met antwoorden.",
|
||||
"com_agents_all": "Alle Agents",
|
||||
"com_agents_by_librechat": "door LibreChat",
|
||||
"com_agents_code_interpreter": "Indien ingeschakeld, kan je agent de LibreChat Code Interpreter API gebruiken om gegenereerde code, inclusief het verwerken van bestanden, veilig uit te voeren. Vereist een geldige API-sleutel.",
|
||||
"com_agents_code_interpreter_title": "Code Interpreter API",
|
||||
|
||||
@@ -478,6 +478,7 @@
|
||||
"com_nav_lang_arabic": "العربية",
|
||||
"com_nav_lang_armenian": "Հայերեն",
|
||||
"com_nav_lang_auto": "自动检测语言",
|
||||
"com_nav_lang_bosnian": "Босански",
|
||||
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
|
||||
"com_nav_lang_catalan": "Català",
|
||||
"com_nav_lang_chinese": "中文",
|
||||
@@ -497,6 +498,7 @@
|
||||
"com_nav_lang_japanese": "日本語",
|
||||
"com_nav_lang_korean": "한국어",
|
||||
"com_nav_lang_latvian": "Latviski",
|
||||
"com_nav_lang_norwegian_bokmal": "Norsk Bokmål",
|
||||
"com_nav_lang_persian": "فارسی",
|
||||
"com_nav_lang_polish": "Polski",
|
||||
"com_nav_lang_portuguese": "Português",
|
||||
|
||||
@@ -4,12 +4,24 @@
|
||||
"com_a11y_ai_composing": "AI 仍在撰寫中",
|
||||
"com_a11y_end": "AI 已完成回覆",
|
||||
"com_a11y_start": "AI 已開始回覆。",
|
||||
"com_agents_agent_card_label": "{{name}} agent。{{description}}",
|
||||
"com_agents_all": "全部 Agent",
|
||||
"com_agents_by_librechat": "由 LibreChat 提供",
|
||||
"com_agents_category_empty": "在 {{category}} 類別中找不到 agent",
|
||||
"com_agents_category_tab_label": "{{category}} 類別,{{position}} / {{total}}",
|
||||
"com_agents_category_tabs_label": "Agent 類別",
|
||||
"com_agents_clear_search": "清除搜尋",
|
||||
"com_agents_code_interpreter": "啟用後,您的代理可以安全地使用 LibreChat 程式碼解譯器 API 來執行產生的程式碼,包括檔案處理功能。需要有效的 API 金鑰。",
|
||||
"com_agents_code_interpreter_title": "程式碼解譯器 API",
|
||||
"com_agents_contact": "關聯",
|
||||
"com_agents_copy_link": "複製連結",
|
||||
"com_agents_create_error": "建立您的代理時發生錯誤。",
|
||||
"com_agents_created_by": "來自",
|
||||
"com_agents_description_placeholder": "選填:在此描述您的代理程式",
|
||||
"com_agents_empty_state_heading": "未找到 agent",
|
||||
"com_agents_enable_file_search": "啟用檔案搜尋",
|
||||
"com_agents_error_bad_request_message": "無法處理該請求",
|
||||
"com_agents_error_bad_request_suggestion": "請檢查您的輸入並再試一次。",
|
||||
"com_agents_file_context": "文件內容 (OCR)",
|
||||
"com_agents_file_context_disabled": "在為檔案上下文上傳檔案之前,必須先建立 Agent。",
|
||||
"com_agents_file_context_info": "以「Context」標記上傳的檔案會使用 OCR 擷取文字,擷取後的文字會被加入到 Agent 的指示中。適合用於文件、含文字的圖片或需要取得檔案完整文字內容的 PDF。",
|
||||
@@ -229,7 +241,7 @@
|
||||
"com_endpoint_openai_max_tokens": "可選的 `max_tokens` 欄位,代表在對話完成中可以生成的最大 token 數。\n\n輸入 token 和生成 token 的總長度受限於模型的上下文長度。如果此數字超過最大上下文 token 數,您可能會遇到錯誤。",
|
||||
"com_endpoint_openai_pres": "數值範圍介於 -2.0 和 2.0 之間。正值會根據該 token 是否在目前的文字中出現來進行懲罰,增加模型談及新主題的可能性。",
|
||||
"com_endpoint_openai_prompt_prefix_placeholder": "在系統訊息中設定自訂提示。",
|
||||
"com_endpoint_openai_reasoning_effort": "僅適用於推理模型:限制推理的投入。降低推理投入可以使回應更快,且在回應中使用較少的推理 token。「Minimal」會產生非常少的推理 token,以達到最快的首次 token 產生時間,特別適合程式碼與指令遵循。",
|
||||
"com_endpoint_openai_reasoning_effort": "僅適用於推理模型:限制推理的投入。降低推理投入可以使回應更快,且在回應中使用較少的推理 token。最小值會產生非常少的推理 token,以達到最快的首次 token 產生時間,特別適合程式碼與指令遵循。",
|
||||
"com_endpoint_openai_reasoning_summary": "僅限 Responses API:模型執行推理的摘要。這有助於除錯並理解模型的推理過程。可設定為 無、自動、簡潔或詳細。",
|
||||
"com_endpoint_openai_resend": "重新傳送之前所有附加的圖片。注意:這可能會大幅增加 token 成本,如果附加了太多圖片,您可能會遇到錯誤。",
|
||||
"com_endpoint_openai_resend_files": "重新傳送之前附加的所有檔案。注意:這將增加 token 成本,如果附件過多,您可能會遇到錯誤。",
|
||||
@@ -238,7 +250,7 @@
|
||||
"com_endpoint_openai_topp": "與溫度取樣的替代方法,稱為核心取樣,其中模型考慮 top_p 機率質量的 token 結果。所以 0.1 表示只考慮佔 top 10% 機率質量的 token。我們建議修改這個或溫度,但不建議兩者都修改。",
|
||||
"com_endpoint_openai_use_responses_api": "使用 Responses API 取代 Chat Completions,Responses API 包含來自 OpenAI 的延伸功能。使用 o1-pro、o3-pro 或要啟用推理摘要時,必須使用 Responses API。",
|
||||
"com_endpoint_openai_use_web_search": "啟用 OpenAI 內建的網路搜尋功能,使模型能夠在網路上搜尋最新資訊,從而提供更準確、即時的回應。",
|
||||
"com_endpoint_openai_verbosity": "限制模型回應的冗長程度。較低的值會得到較簡潔的回應,較高的值會得到較冗長的回應。目前支援的值為 low、medium 與 high。",
|
||||
"com_endpoint_openai_verbosity": "限制模型回應的詳盡程度。數值較低會產生較精簡的回應,數值較高會產生較詳盡的回應。目前支援的值為低/中/高。",
|
||||
"com_endpoint_output": "輸出",
|
||||
"com_endpoint_plug_image_detail": "影像詳細資訊",
|
||||
"com_endpoint_plug_resend_files": "重新傳送檔案",
|
||||
@@ -280,6 +292,7 @@
|
||||
"com_endpoint_top_k": "Top K",
|
||||
"com_endpoint_top_p": "Top P",
|
||||
"com_endpoint_use_active_assistant": "使用活躍助理",
|
||||
"com_endpoint_verbosity": "詳盡程度",
|
||||
"com_error_expired_user_key": "提供給 {{0}} 的金鑰已於 {{1}} 到期。請提供一個新的金鑰並重試。",
|
||||
"com_error_files_dupe": "偵測到重複的檔案。",
|
||||
"com_error_files_empty": "不允許空白檔案。",
|
||||
@@ -318,6 +331,7 @@
|
||||
"com_nav_balance": "餘額",
|
||||
"com_nav_balance_day": "日",
|
||||
"com_nav_balance_days": "日",
|
||||
"com_nav_balance_every": "每",
|
||||
"com_nav_balance_hour": "小時",
|
||||
"com_nav_balance_hours": "小時",
|
||||
"com_nav_balance_interval": "間隔",
|
||||
@@ -386,9 +400,14 @@
|
||||
"com_nav_info_save_draft": "啟用後,您在聊天表單中輸入的文字和附件將自動儲存為本地草稿。即使重新載入頁面或切換至其他對話,這些草稿仍會保留。草稿僅儲存在您的裝置上,並會在訊息送出後自動刪除。",
|
||||
"com_nav_info_user_name_display": "啟用時,每則您發送的訊息上方都會顯示您的使用者名稱。停用時,您的訊息上方只會顯示「您」。",
|
||||
"com_nav_lang_arabic": "العربية",
|
||||
"com_nav_lang_armenian": "Հայերեն",
|
||||
"com_nav_lang_auto": "自動偵測",
|
||||
"com_nav_lang_bosnian": "Босански",
|
||||
"com_nav_lang_brazilian_portuguese": "Português Brasileiro",
|
||||
"com_nav_lang_catalan": "Català",
|
||||
"com_nav_lang_chinese": "中文",
|
||||
"com_nav_lang_czech": "Čeština",
|
||||
"com_nav_lang_danish": "Dansk",
|
||||
"com_nav_lang_dutch": "Nederlands",
|
||||
"com_nav_lang_english": "English",
|
||||
"com_nav_lang_estonian": "Eesti keel",
|
||||
@@ -397,18 +416,25 @@
|
||||
"com_nav_lang_georgian": "ქართული",
|
||||
"com_nav_lang_german": "Deutsch",
|
||||
"com_nav_lang_hebrew": "עברית",
|
||||
"com_nav_lang_hungarian": "Magyar",
|
||||
"com_nav_lang_indonesia": "Indonesia",
|
||||
"com_nav_lang_italian": "Italiano",
|
||||
"com_nav_lang_japanese": "日本語",
|
||||
"com_nav_lang_korean": "한국어",
|
||||
"com_nav_lang_latvian": "Latviski",
|
||||
"com_nav_lang_norwegian_bokmal": "Norsk Bokmål",
|
||||
"com_nav_lang_persian": "فارسی",
|
||||
"com_nav_lang_polish": "Polski",
|
||||
"com_nav_lang_portuguese": "Português",
|
||||
"com_nav_lang_russian": "Русский",
|
||||
"com_nav_lang_spanish": "Español",
|
||||
"com_nav_lang_swedish": "Svenska",
|
||||
"com_nav_lang_thai": "ไทย",
|
||||
"com_nav_lang_tibetan": "བོད་སྐད་",
|
||||
"com_nav_lang_traditional_chinese": "繁體中文",
|
||||
"com_nav_lang_turkish": "Türkçe",
|
||||
"com_nav_lang_ukrainian": "Українська",
|
||||
"com_nav_lang_uyghur": "Uyƣur tili",
|
||||
"com_nav_lang_vietnamese": "Tiếng Việt",
|
||||
"com_nav_language": "語言",
|
||||
"com_nav_latex_parsing": "解析訊息中的 LaTeX 內容(可能影響效能)",
|
||||
@@ -531,6 +557,7 @@
|
||||
"com_ui_attachment": "附件",
|
||||
"com_ui_auth_type": "認證類型",
|
||||
"com_ui_authentication": "驗證",
|
||||
"com_ui_auto": "自動",
|
||||
"com_ui_avatar": "大頭照",
|
||||
"com_ui_back_to_chat": "返回對話",
|
||||
"com_ui_back_to_prompts": "返回提示",
|
||||
@@ -562,6 +589,7 @@
|
||||
"com_ui_collapse_chat": "收合對話",
|
||||
"com_ui_command_placeholder": "選填:輸入指令,若未填寫將使用名稱",
|
||||
"com_ui_command_usage_placeholder": "透過指令或名稱選擇提示",
|
||||
"com_ui_concise": "簡潔",
|
||||
"com_ui_confirm_action": "確認操作",
|
||||
"com_ui_context": "情境",
|
||||
"com_ui_continue": "繼續",
|
||||
@@ -613,6 +641,7 @@
|
||||
"com_ui_description": "描述",
|
||||
"com_ui_description_placeholder": "選填:輸入要顯示的提示描述",
|
||||
"com_ui_deselect_all": "全部取消選取",
|
||||
"com_ui_detailed": "詳細",
|
||||
"com_ui_download_error": "下載檔案時發生錯誤。該檔案可能已被刪除。",
|
||||
"com_ui_dropdown_variables": "下拉式變數:",
|
||||
"com_ui_dropdown_variables_info": "為您的提示建立自訂下拉選單:`{{variable_name:選項1|選項2|選項3}}`",
|
||||
@@ -621,6 +650,7 @@
|
||||
"com_ui_duplication_processing": "正在複製對話...",
|
||||
"com_ui_duplication_success": "已成功複製對話",
|
||||
"com_ui_edit": "編輯",
|
||||
"com_ui_empty_category": "-",
|
||||
"com_ui_endpoint": "端點",
|
||||
"com_ui_endpoint_menu": "語言模型端點選單",
|
||||
"com_ui_enter": "輸入",
|
||||
@@ -658,7 +688,10 @@
|
||||
"com_ui_generate_qrcode": "產生 QR 碼",
|
||||
"com_ui_generating": "產生中...",
|
||||
"com_ui_go_to_conversation": "前往對話",
|
||||
"com_ui_good_evening": "晚安",
|
||||
"com_ui_good_morning": "早安",
|
||||
"com_ui_happy_birthday": "這是我的第一個生日!",
|
||||
"com_ui_high": "高",
|
||||
"com_ui_host": "主機",
|
||||
"com_ui_image_gen": "影像生成",
|
||||
"com_ui_import_conversation_error": "匯入對話時發生錯誤",
|
||||
@@ -668,17 +701,20 @@
|
||||
"com_ui_include_shadcnui": "包含 shadcn/ui 元件說明",
|
||||
"com_ui_input": "輸入",
|
||||
"com_ui_instructions": "說明",
|
||||
"com_ui_key": "鍵",
|
||||
"com_ui_latest_footer": "讓每個人都能使用 AI",
|
||||
"com_ui_librechat_code_api_key": "取得你的 LibreChat 程式碼解譯器 API 金鑰",
|
||||
"com_ui_librechat_code_api_subtitle": "安全性高。多語言支援。檔案輸入/輸出。",
|
||||
"com_ui_librechat_code_api_title": "執行 AI 程式碼",
|
||||
"com_ui_locked": "已鎖定",
|
||||
"com_ui_logo": "{{0}} 標誌",
|
||||
"com_ui_low": "低",
|
||||
"com_ui_manage": "管理",
|
||||
"com_ui_max_tags": "允許的最大數量為 {{0}},已使用最新值。",
|
||||
"com_ui_mcp_enter_var": "請輸入 {{0}} 的值",
|
||||
"com_ui_mcp_server_not_found": "找不到伺服器。",
|
||||
"com_ui_mcp_url": "MCP 伺服器",
|
||||
"com_ui_medium": "中",
|
||||
"com_ui_memories": "記憶",
|
||||
"com_ui_memories_allow_create": "允許建立記憶",
|
||||
"com_ui_memories_allow_opt_out": "允許用戶選擇不使用記憶功能",
|
||||
@@ -700,21 +736,25 @@
|
||||
"com_ui_memory_would_exceed": "無法儲存——會超出限制 {{tokens}} 個 token。請刪除現有記憶以釋放空間。",
|
||||
"com_ui_mention": "提及端點、助理或預設設定以快速切換",
|
||||
"com_ui_min_tags": "無法再移除更多值,至少需要 {{0}} 個。",
|
||||
"com_ui_minimal": "最小值",
|
||||
"com_ui_model": "模型",
|
||||
"com_ui_model_parameters": "模型參數",
|
||||
"com_ui_more_info": "更多資訊",
|
||||
"com_ui_my_prompts": "我的提示",
|
||||
"com_ui_name": "名稱",
|
||||
"com_ui_new": "新",
|
||||
"com_ui_new_chat": "新對話",
|
||||
"com_ui_next": "下一個",
|
||||
"com_ui_no": "否",
|
||||
"com_ui_no_bookmarks": "看來您還沒有任何書籤。請點選對話並新增一個書籤",
|
||||
"com_ui_no_category": "無分類",
|
||||
"com_ui_no_terms_content": "沒有條款和條件內容顯示",
|
||||
"com_ui_none": "無",
|
||||
"com_ui_nothing_found": "找不到任何內容",
|
||||
"com_ui_of": "的",
|
||||
"com_ui_off": "關閉",
|
||||
"com_ui_on": "開啟",
|
||||
"com_ui_openai": "OpenAI",
|
||||
"com_ui_page": "頁面",
|
||||
"com_ui_prev": "上一個",
|
||||
"com_ui_preview": "預覽",
|
||||
@@ -763,6 +803,7 @@
|
||||
"com_ui_select_search_plugin": "依名稱搜尋外掛程式",
|
||||
"com_ui_select_search_provider": "依名稱搜尋供應商",
|
||||
"com_ui_select_search_region": "依名稱搜尋區域",
|
||||
"com_ui_set": "設置",
|
||||
"com_ui_share": "分享",
|
||||
"com_ui_share_create_message": "您的姓名以及您在共享後新增的任何訊息都會保密。",
|
||||
"com_ui_share_delete_error": "刪除共享連結時發生錯誤。",
|
||||
@@ -780,8 +821,11 @@
|
||||
"com_ui_stop": "停止",
|
||||
"com_ui_storage": "儲存空間",
|
||||
"com_ui_submit": "送出",
|
||||
"com_ui_support_contact_email": "Email",
|
||||
"com_ui_terms_and_conditions": "條款和條件",
|
||||
"com_ui_terms_of_service": "服務條款",
|
||||
"com_ui_thoughts": "思考過程",
|
||||
"com_ui_token": "token",
|
||||
"com_ui_tools": "工具",
|
||||
"com_ui_unarchive": "取消封存",
|
||||
"com_ui_unarchive_error": "取消封存對話時發生錯誤",
|
||||
@@ -825,6 +869,7 @@
|
||||
"com_ui_web_search_searxng_instance_url": "SearXNG Instance URL",
|
||||
"com_ui_web_searching": "正在搜尋網路",
|
||||
"com_ui_web_searching_again": "正在重新搜尋網路",
|
||||
"com_ui_weekend_morning": "週末愉快",
|
||||
"com_ui_yes": "是",
|
||||
"com_ui_zoom": "縮放",
|
||||
"com_user_message": "您"
|
||||
|
||||
@@ -207,4 +207,36 @@ y$ which spans lines`;
|
||||
const expected = 'Set $$\\{x | x > \\$0\\}$$ for positive prices';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
|
||||
test('does not convert when closing dollar is preceded by backtick', () => {
|
||||
const content = 'The error "invalid $lookup namespace" occurs when using `$lookup` operator';
|
||||
const expected = 'The error "invalid $lookup namespace" occurs when using `$lookup` operator';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
|
||||
test('handles mixed backtick and non-backtick cases', () => {
|
||||
const content = 'Use $x + y$ in math but `$lookup` in code';
|
||||
const expected = 'Use $$x + y$$ in math but `$lookup` in code';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
|
||||
test('escapes currency amounts without commas', () => {
|
||||
const content =
|
||||
'The total amount invested is $1157.90 (existing amount) + $500 (new investment) = $1657.90.';
|
||||
const expected =
|
||||
'The total amount invested is \\$1157.90 (existing amount) + \\$500 (new investment) = \\$1657.90.';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
|
||||
test('handles large currency amounts', () => {
|
||||
const content = 'You can win $1000000 or even $9999999.99!';
|
||||
const expected = 'You can win \\$1000000 or even \\$9999999.99!';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
|
||||
test('escapes currency with many decimal places', () => {
|
||||
const content = 'Bitcoin: $0.00001234, Gas: $3.999, Rate: $1.234567890';
|
||||
const expected = 'Bitcoin: \\$0.00001234, Gas: \\$3.999, Rate: \\$1.234567890';
|
||||
expect(preprocessLaTeX(content)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,9 +3,8 @@ const MHCHEM_CE_REGEX = /\$\\ce\{/g;
|
||||
const MHCHEM_PU_REGEX = /\$\\pu\{/g;
|
||||
const MHCHEM_CE_ESCAPED_REGEX = /\$\\\\ce\{[^}]*\}\$/g;
|
||||
const MHCHEM_PU_ESCAPED_REGEX = /\$\\\\pu\{[^}]*\}\$/g;
|
||||
const CURRENCY_REGEX =
|
||||
/(?<![\\$])\$(?!\$)(?=\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?(?:\s|$|[^a-zA-Z\d]))/g;
|
||||
const SINGLE_DOLLAR_REGEX = /(?<!\\)\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)\$(?!\$)/g;
|
||||
const CURRENCY_REGEX = /(?<![\\$])\$(?!\$)(?=\d+(?:,\d{3})*(?:\.\d+)?(?:\s|$|[^a-zA-Z\d]))/g;
|
||||
const SINGLE_DOLLAR_REGEX = /(?<!\\)\$(?!\$)((?:[^$\n]|\\[$])+?)(?<!\\)(?<!`)\$(?!\$)/g;
|
||||
|
||||
/**
|
||||
* Escapes mhchem package notation in LaTeX by converting single dollar delimiters to double dollars
|
||||
|
||||
@@ -16,6 +16,38 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
||||
|
||||
logger.info('Starting Enhanced Agent Permissions Migration', { dryRun, batchSize });
|
||||
|
||||
/** Ensurse `aclentries` collection exists for DocumentDB compatibility
|
||||
* @param {import('mongoose').mongo.Db} db
|
||||
* @param {string} collectionName
|
||||
*/
|
||||
async function ensureCollectionExists(db, collectionName) {
|
||||
try {
|
||||
const collections = await db.listCollections({ name: collectionName }).toArray();
|
||||
if (collections.length === 0) {
|
||||
await db.createCollection(collectionName);
|
||||
logger.info(`Created collection: ${collectionName}`);
|
||||
} else {
|
||||
logger.info(`Collection already exists: ${collectionName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`'Failed to check/create "${collectionName}" collection:`, error);
|
||||
// If listCollections fails, try alternative approach
|
||||
try {
|
||||
// Try to access the collection directly - this will create it in MongoDB if it doesn't exist
|
||||
await db.collection(collectionName).findOne({}, { projection: { _id: 1 } });
|
||||
} catch (createError) {
|
||||
logger.error(`Could not ensure collection ${collectionName} exists:`, createError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
/** @type {import('mongoose').mongo.Db | undefined} */
|
||||
const db = mongoose.connection.db;
|
||||
if (db) {
|
||||
await ensureCollectionExists(db, 'aclentries');
|
||||
}
|
||||
|
||||
// Verify required roles exist
|
||||
const ownerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_OWNER);
|
||||
const viewerRole = await findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
@@ -100,12 +132,19 @@ async function migrateAgentPermissionsEnhanced({ dryRun = true, batchSize = 100
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('Agent categorization:', {
|
||||
globalEditAccess: categories.globalEditAccess.length,
|
||||
globalViewAccess: categories.globalViewAccess.length,
|
||||
privateAgents: categories.privateAgents.length,
|
||||
total: agentsToMigrate.length,
|
||||
});
|
||||
logger.info(
|
||||
'Agent categorization:\n' +
|
||||
JSON.stringify(
|
||||
{
|
||||
globalEditAccess: categories.globalEditAccess.length,
|
||||
globalViewAccess: categories.globalViewAccess.length,
|
||||
privateAgents: categories.privateAgents.length,
|
||||
total: agentsToMigrate.length,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
if (dryRun) {
|
||||
return {
|
||||
|
||||
@@ -16,6 +16,38 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
||||
|
||||
logger.info('Starting PromptGroup Permissions Migration', { dryRun, batchSize });
|
||||
|
||||
/** Ensurse `aclentries` collection exists for DocumentDB compatibility
|
||||
* @param {import('mongoose').mongo.Db} db
|
||||
* @param {string} collectionName
|
||||
*/
|
||||
async function ensureCollectionExists(db, collectionName) {
|
||||
try {
|
||||
const collections = await db.listCollections({ name: collectionName }).toArray();
|
||||
if (collections.length === 0) {
|
||||
await db.createCollection(collectionName);
|
||||
logger.info(`Created collection: ${collectionName}`);
|
||||
} else {
|
||||
logger.info(`Collection already exists: ${collectionName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`'Failed to check/create "${collectionName}" collection:`, error);
|
||||
// If listCollections fails, try alternative approach
|
||||
try {
|
||||
// Try to access the collection directly - this will create it in MongoDB if it doesn't exist
|
||||
await db.collection(collectionName).findOne({}, { projection: { _id: 1 } });
|
||||
} catch (createError) {
|
||||
logger.error(`Could not ensure collection ${collectionName} exists:`, createError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
/** @type {import('mongoose').mongo.Db | undefined} */
|
||||
const db = mongoose.connection.db;
|
||||
if (db) {
|
||||
await ensureCollectionExists(db, 'aclentries');
|
||||
}
|
||||
|
||||
// Verify required roles exist
|
||||
const ownerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER);
|
||||
const viewerRole = await findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER);
|
||||
@@ -91,11 +123,18 @@ async function migrateToPromptGroupPermissions({ dryRun = true, batchSize = 100
|
||||
}
|
||||
});
|
||||
|
||||
logger.info('PromptGroup categorization:', {
|
||||
globalViewAccess: categories.globalViewAccess.length,
|
||||
privateGroups: categories.privateGroups.length,
|
||||
total: promptGroupsToMigrate.length,
|
||||
});
|
||||
logger.info(
|
||||
'PromptGroup categorization:\n' +
|
||||
JSON.stringify(
|
||||
{
|
||||
globalViewAccess: categories.globalViewAccess.length,
|
||||
privateGroups: categories.privateGroups.length,
|
||||
total: promptGroupsToMigrate.length,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
|
||||
if (dryRun) {
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// v0.8.0-rc2
|
||||
// v0.8.0-rc3
|
||||
// See .env.test.example for an example of the '.env.test' file.
|
||||
require('dotenv').config({ path: './e2e/.env.test' });
|
||||
|
||||
@@ -23,7 +23,7 @@ version: 1.8.10
|
||||
# It is recommended to use it with quotes.
|
||||
|
||||
# renovate: image=ghcr.io/danny-avila/librechat
|
||||
appVersion: "v0.8.0-rc2"
|
||||
appVersion: "v0.8.0-rc3"
|
||||
|
||||
home: https://www.librechat.ai
|
||||
|
||||
|
||||
466
package-lock.json
generated
466
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "LibreChat",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "LibreChat",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"license": "ISC",
|
||||
"workspaces": [
|
||||
"api",
|
||||
@@ -46,7 +46,7 @@
|
||||
},
|
||||
"api": {
|
||||
"name": "@librechat/backend",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.52.0",
|
||||
@@ -2623,7 +2623,7 @@
|
||||
},
|
||||
"client": {
|
||||
"name": "@librechat/frontend",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ariakit/react": "^0.4.15",
|
||||
@@ -17654,6 +17654,454 @@
|
||||
"kuler": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/adventurer": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/adventurer/-/adventurer-9.2.4.tgz",
|
||||
"integrity": "sha512-Xvboay3VH1qe7lH17T+bA3qPawf5EjccssDiyhCX/VT0P21c65JyjTIUJV36Nsv08HKeyDscyP0kgt9nPTRKvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/adventurer-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/adventurer-neutral/-/adventurer-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-I9IrB4ZYbUHSOUpWoUbfX3vG8FrjcW8htoQ4bEOR7TYOKKE11Mo1nrGMuHZ7GPfwN0CQeK1YVJhWqLTmtYn7Pg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/avataaars": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/avataaars/-/avataaars-9.2.4.tgz",
|
||||
"integrity": "sha512-QKNBtA/1QGEzR+JjS4XQyrFHYGbzdOp0oa6gjhGhUDrMegDFS8uyjdRfDQsFTebVkyLWjgBQKZEiDqKqHptB6A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/avataaars-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/avataaars-neutral/-/avataaars-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-HtBvA7elRv50QTOOsBdtYB1GVimCpGEDlDgWsu1snL5Z3d1+3dIESoXQd3mXVvKTVT8Z9ciA4TEaF09WfxDjAA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/big-ears": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/big-ears/-/big-ears-9.2.4.tgz",
|
||||
"integrity": "sha512-U33tbh7Io6wG6ViUMN5fkWPER7hPKMaPPaYgafaYQlCT4E7QPKF2u8X1XGag3jCKm0uf4SLXfuZ8v+YONcHmNQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/big-ears-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/big-ears-neutral/-/big-ears-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-pPjYu80zMFl43A9sa5+tAKPkhp4n9nd7eN878IOrA1HAowh/XePh5JN8PTkNFS9eM+rnN9m8WX08XYFe30kLYw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/big-smile": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/big-smile/-/big-smile-9.2.4.tgz",
|
||||
"integrity": "sha512-zeEfXOOXy7j9tfkPLzfQdLBPyQsctBetTdEfKRArc1k3RUliNPxfJG9j88+cXQC6GXrVW2pcT2X50NSPtugCFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/bottts": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/bottts/-/bottts-9.2.4.tgz",
|
||||
"integrity": "sha512-4CTqrnVg+NQm6lZ4UuCJish8gGWe8EqSJrzvHQRO5TEyAKjYxbTdVqejpkycG1xkawha4FfxsYgtlSx7UwoVMw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/bottts-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/bottts-neutral/-/bottts-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-eMVdofdD/udHsKIaeWEXShDRtiwk7vp4FjY7l0f79vIzfhkIsXKEhPcnvHKOl/yoArlDVS3Uhgjj0crWTO9RJA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/collection": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/collection/-/collection-9.2.4.tgz",
|
||||
"integrity": "sha512-I1wCUp0yu5qSIeMQHmDYXQIXKkKjcja/SYBxppPkYFXpR2alxb0k9/swFDdMbkY6a1c9AT1kI1y+Pg6ywQ2rTA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dicebear/adventurer": "9.2.4",
|
||||
"@dicebear/adventurer-neutral": "9.2.4",
|
||||
"@dicebear/avataaars": "9.2.4",
|
||||
"@dicebear/avataaars-neutral": "9.2.4",
|
||||
"@dicebear/big-ears": "9.2.4",
|
||||
"@dicebear/big-ears-neutral": "9.2.4",
|
||||
"@dicebear/big-smile": "9.2.4",
|
||||
"@dicebear/bottts": "9.2.4",
|
||||
"@dicebear/bottts-neutral": "9.2.4",
|
||||
"@dicebear/croodles": "9.2.4",
|
||||
"@dicebear/croodles-neutral": "9.2.4",
|
||||
"@dicebear/dylan": "9.2.4",
|
||||
"@dicebear/fun-emoji": "9.2.4",
|
||||
"@dicebear/glass": "9.2.4",
|
||||
"@dicebear/icons": "9.2.4",
|
||||
"@dicebear/identicon": "9.2.4",
|
||||
"@dicebear/initials": "9.2.4",
|
||||
"@dicebear/lorelei": "9.2.4",
|
||||
"@dicebear/lorelei-neutral": "9.2.4",
|
||||
"@dicebear/micah": "9.2.4",
|
||||
"@dicebear/miniavs": "9.2.4",
|
||||
"@dicebear/notionists": "9.2.4",
|
||||
"@dicebear/notionists-neutral": "9.2.4",
|
||||
"@dicebear/open-peeps": "9.2.4",
|
||||
"@dicebear/personas": "9.2.4",
|
||||
"@dicebear/pixel-art": "9.2.4",
|
||||
"@dicebear/pixel-art-neutral": "9.2.4",
|
||||
"@dicebear/rings": "9.2.4",
|
||||
"@dicebear/shapes": "9.2.4",
|
||||
"@dicebear/thumbs": "9.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/core": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.2.4.tgz",
|
||||
"integrity": "sha512-hz6zArEcUwkZzGOSJkWICrvqnEZY7BKeiq9rqKzVJIc1tRVv0MkR0FGvIxSvXiK9TTIgKwu656xCWAGAl6oh+w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/croodles": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/croodles/-/croodles-9.2.4.tgz",
|
||||
"integrity": "sha512-CqT0NgVfm+5kd+VnjGY4WECNFeOrj5p7GCPTSEA7tCuN72dMQOX47P9KioD3wbExXYrIlJgOcxNrQeb/FMGc3A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/croodles-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/croodles-neutral/-/croodles-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-8vAS9lIEKffSUVx256GSRAlisB8oMX38UcPWw72venO/nitLVsyZ6hZ3V7eBdII0Onrjqw1RDndslQODbVcpTw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/dylan": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/dylan/-/dylan-9.2.4.tgz",
|
||||
"integrity": "sha512-tiih1358djAq0jDDzmW3N3S4C3ynC2yn4hhlTAq/MaUAQtAi47QxdHdFGdxH0HBMZKqA4ThLdVk3yVgN4xsukg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/fun-emoji": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/fun-emoji/-/fun-emoji-9.2.4.tgz",
|
||||
"integrity": "sha512-Od729skczse1HvHekgEFv+mSuJKMC4sl5hENGi/izYNe6DZDqJrrD0trkGT/IVh/SLXUFbq1ZFY9I2LoUGzFZg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/glass": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/glass/-/glass-9.2.4.tgz",
|
||||
"integrity": "sha512-5lxbJode1t99eoIIgW0iwZMoZU4jNMJv/6vbsgYUhAslYFX5zP0jVRscksFuo89TTtS7YKqRqZAL3eNhz4bTDw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/icons": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/icons/-/icons-9.2.4.tgz",
|
||||
"integrity": "sha512-bRsK1qj8u9Z76xs8XhXlgVr/oHh68tsHTJ/1xtkX9DeTQTSamo2tS26+r231IHu+oW3mePtFnwzdG9LqEPRd4A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/identicon": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/identicon/-/identicon-9.2.4.tgz",
|
||||
"integrity": "sha512-R9nw/E8fbu9HltHOqI9iL/o9i7zM+2QauXWMreQyERc39oGR9qXiwgBxsfYGcIS4C85xPyuL5B3I2RXrLBlJPg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/initials": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/initials/-/initials-9.2.4.tgz",
|
||||
"integrity": "sha512-4SzHG5WoQZl1TGcpEZR4bdsSkUVqwNQCOwWSPAoBJa3BNxbVsvL08LF7I97BMgrCoknWZjQHUYt05amwTPTKtg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/lorelei": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/lorelei/-/lorelei-9.2.4.tgz",
|
||||
"integrity": "sha512-eS4mPYUgDpo89HvyFAx/kgqSSKh8W4zlUA8QJeIUCWTB0WpQmeqkSgIyUJjGDYSrIujWi+zEhhckksM5EwW0Dg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/lorelei-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/lorelei-neutral/-/lorelei-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-bWq2/GonbcJULtT+B/MGcM2UnA7kBQoH+INw8/oW83WI3GNTZ6qEwe3/W4QnCgtSOhUsuwuiSULguAFyvtkOZQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/micah": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/micah/-/micah-9.2.4.tgz",
|
||||
"integrity": "sha512-XNWJ8Mx+pncIV8Ye0XYc/VkMiax8kTxcP3hLTC5vmELQyMSLXzg/9SdpI+W/tCQghtPZRYTT3JdY9oU9IUlP2g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/miniavs": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/miniavs/-/miniavs-9.2.4.tgz",
|
||||
"integrity": "sha512-k7IYTAHE/4jSO6boMBRrNlqPT3bh7PLFM1atfe0nOeCDwmz/qJUBP3HdONajbf3fmo8f2IZYhELrNWTOE7Ox3Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/notionists": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/notionists/-/notionists-9.2.4.tgz",
|
||||
"integrity": "sha512-zcvpAJ93EfC0xQffaPZQuJPShwPhnu9aTcoPsaYGmw0oEDLcv2XYmDhUUdX84QYCn6LtCZH053rHLVazRW+OGw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/notionists-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/notionists-neutral/-/notionists-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-fskWzBVxQzJhCKqY24DGZbYHSBaauoRa1DgXM7+7xBuksH7mfbTmZTvnUAsAqJYBkla8IPb4ERKduDWtlWYYjQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/open-peeps": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/open-peeps/-/open-peeps-9.2.4.tgz",
|
||||
"integrity": "sha512-s6nwdjXFsplqEI7imlsel4Gt6kFVJm6YIgtZSpry0UdwDoxUUudei5bn957j9lXwVpVUcRjJW+TuEKztYjXkKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/personas": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/personas/-/personas-9.2.4.tgz",
|
||||
"integrity": "sha512-JNim8RfZYwb0MfxW6DLVfvreCFIevQg+V225Xe5tDfbFgbcYEp4OU/KaiqqO2476OBjCw7i7/8USbv2acBhjwA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/pixel-art": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/pixel-art/-/pixel-art-9.2.4.tgz",
|
||||
"integrity": "sha512-4Ao45asieswUdlCTBZqcoF/0zHR3OWUWB0Mvhlu9b1Fbc6IlPBiOfx2vsp6bnVGVnMag58tJLecx2omeXdECBQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/pixel-art-neutral": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/pixel-art-neutral/-/pixel-art-neutral-9.2.4.tgz",
|
||||
"integrity": "sha512-ZITPLD1cPN4GjKkhWi80s7e5dcbXy34ijWlvmxbc4eb/V7fZSsyRa9EDUW3QStpo+xrCJLcLR+3RBE5iz0PC/A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/rings": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/rings/-/rings-9.2.4.tgz",
|
||||
"integrity": "sha512-teZxELYyV2ogzgb5Mvtn/rHptT0HXo9SjUGS4A52mOwhIdHSGGU71MqA1YUzfae9yJThsw6K7Z9kzuY2LlZZHA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/shapes": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/shapes/-/shapes-9.2.4.tgz",
|
||||
"integrity": "sha512-MhK9ZdFm1wUnH4zWeKPRMZ98UyApolf5OLzhCywfu38tRN6RVbwtBRHc/42ZwoN1JU1JgXr7hzjYucMqISHtbA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dicebear/thumbs": {
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@dicebear/thumbs/-/thumbs-9.2.4.tgz",
|
||||
"integrity": "sha512-EL4sMqv9p2+1Xy3d8e8UxyeKZV2+cgt3X2x2RTRzEOIIhobtkL8u6lJxmJbiGbpVtVALmrt5e7gjmwqpryYDpg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dicebear/core": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz",
|
||||
@@ -51358,7 +51806,7 @@
|
||||
},
|
||||
"packages/api": {
|
||||
"name": "@librechat/api",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.21.5",
|
||||
@@ -51484,7 +51932,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@librechat/client",
|
||||
"version": "0.2.6",
|
||||
"version": "0.2.7",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.2",
|
||||
@@ -51512,6 +51960,8 @@
|
||||
"peerDependencies": {
|
||||
"@ariakit/react": "^0.4.16",
|
||||
"@ariakit/react-core": "^0.4.17",
|
||||
"@dicebear/collection": "^9.2.2",
|
||||
"@dicebear/core": "^9.2.2",
|
||||
"@headlessui/react": "^2.1.2",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
@@ -51783,7 +52233,7 @@
|
||||
},
|
||||
"packages/data-provider": {
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.8.003",
|
||||
"version": "0.8.004",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.8.2",
|
||||
@@ -51888,7 +52338,7 @@
|
||||
},
|
||||
"packages/data-schemas": {
|
||||
"name": "@librechat/data-schemas",
|
||||
"version": "0.0.19",
|
||||
"version": "0.0.20",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "LibreChat",
|
||||
"version": "v0.8.0-rc2",
|
||||
"version": "v0.8.0-rc3",
|
||||
"description": "",
|
||||
"workspaces": [
|
||||
"api",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/api",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"type": "commonjs",
|
||||
"description": "MCP services for LibreChat",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider';
|
||||
import type { AccessRoleMethods, IAgent } from '@librechat/data-schemas';
|
||||
import type { Model } from 'mongoose';
|
||||
import type { Model, Mongoose, mongo } from 'mongoose';
|
||||
|
||||
const { GLOBAL_PROJECT_NAME } = Constants;
|
||||
|
||||
@@ -17,7 +17,8 @@ export interface MigrationCheckDbMethods {
|
||||
}
|
||||
|
||||
export interface MigrationCheckParams {
|
||||
db: MigrationCheckDbMethods;
|
||||
mongoose: Mongoose;
|
||||
methods: MigrationCheckDbMethods;
|
||||
AgentModel: Model<IAgent>;
|
||||
}
|
||||
|
||||
@@ -46,16 +47,44 @@ export interface MigrationCheckResult {
|
||||
* This performs a dry-run check similar to the migration script
|
||||
*/
|
||||
export async function checkAgentPermissionsMigration({
|
||||
db,
|
||||
methods,
|
||||
mongoose,
|
||||
AgentModel,
|
||||
}: MigrationCheckParams): Promise<MigrationCheckResult> {
|
||||
logger.debug('Checking if agent permissions migration is needed');
|
||||
|
||||
try {
|
||||
/** Ensurse `aclentries` collection exists for DocumentDB compatibility */
|
||||
async function ensureCollectionExists(db: mongo.Db, collectionName: string) {
|
||||
try {
|
||||
const collections = await db.listCollections({ name: collectionName }).toArray();
|
||||
if (collections.length === 0) {
|
||||
await db.createCollection(collectionName);
|
||||
logger.info(`Created collection: ${collectionName}`);
|
||||
} else {
|
||||
logger.debug(`Collection already exists: ${collectionName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`'Failed to check/create "${collectionName}" collection:`, error);
|
||||
// If listCollections fails, try alternative approach
|
||||
try {
|
||||
// Try to access the collection directly - this will create it in MongoDB if it doesn't exist
|
||||
await db.collection(collectionName).findOne({}, { projection: { _id: 1 } });
|
||||
} catch (createError) {
|
||||
logger.error(`Could not ensure collection ${collectionName} exists:`, createError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = mongoose.connection.db;
|
||||
if (db) {
|
||||
await ensureCollectionExists(db, 'aclentries');
|
||||
}
|
||||
|
||||
// Verify required roles exist
|
||||
const ownerRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER);
|
||||
const viewerRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
const editorRole = await db.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_OWNER);
|
||||
const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_VIEWER);
|
||||
const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.AGENT_EDITOR);
|
||||
|
||||
if (!ownerRole || !viewerRole || !editorRole) {
|
||||
logger.warn(
|
||||
@@ -70,7 +99,7 @@ export async function checkAgentPermissionsMigration({
|
||||
}
|
||||
|
||||
// Get global project agent IDs
|
||||
const globalProject = await db.getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']);
|
||||
const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']);
|
||||
const globalAgentIds = new Set(globalProject?.agentIds || []);
|
||||
|
||||
// Find agents without ACL entries (no batching for efficiency on startup)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import { AccessRoleIds, ResourceType, PrincipalType, Constants } from 'librechat-data-provider';
|
||||
import type { AccessRoleMethods, IPromptGroupDocument } from '@librechat/data-schemas';
|
||||
import type { Model } from 'mongoose';
|
||||
import type { Model, Mongoose, mongo } from 'mongoose';
|
||||
|
||||
const { GLOBAL_PROJECT_NAME } = Constants;
|
||||
|
||||
@@ -17,7 +17,8 @@ export interface PromptMigrationCheckDbMethods {
|
||||
}
|
||||
|
||||
export interface PromptMigrationCheckParams {
|
||||
db: PromptMigrationCheckDbMethods;
|
||||
mongoose: Mongoose;
|
||||
methods: PromptMigrationCheckDbMethods;
|
||||
PromptGroupModel: Model<IPromptGroupDocument>;
|
||||
}
|
||||
|
||||
@@ -44,16 +45,45 @@ export interface PromptMigrationCheckResult {
|
||||
* This performs a dry-run check similar to the migration script
|
||||
*/
|
||||
export async function checkPromptPermissionsMigration({
|
||||
db,
|
||||
methods,
|
||||
mongoose,
|
||||
PromptGroupModel,
|
||||
}: PromptMigrationCheckParams): Promise<PromptMigrationCheckResult> {
|
||||
logger.debug('Checking if prompt permissions migration is needed');
|
||||
|
||||
try {
|
||||
/** Ensurse `aclentries` collection exists for DocumentDB compatibility */
|
||||
async function ensureCollectionExists(db: mongo.Db, collectionName: string) {
|
||||
try {
|
||||
const collections = await db.listCollections({ name: collectionName }).toArray();
|
||||
if (collections.length === 0) {
|
||||
await db.createCollection(collectionName);
|
||||
logger.info(`Created collection: ${collectionName}`);
|
||||
} else {
|
||||
logger.debug(`Collection already exists: ${collectionName}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`'Failed to check/create "${collectionName}" collection:`, error);
|
||||
// If listCollections fails, try alternative approach
|
||||
try {
|
||||
// Try to access the collection directly - this will create it in MongoDB if it doesn't exist
|
||||
await db.collection(collectionName).findOne({}, { projection: { _id: 1 } });
|
||||
} catch (createError) {
|
||||
logger.error(`Could not ensure collection ${collectionName} exists:`, createError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Native MongoDB database instance */
|
||||
const db = mongoose.connection.db;
|
||||
if (db) {
|
||||
await ensureCollectionExists(db, 'aclentries');
|
||||
}
|
||||
|
||||
// Verify required roles exist
|
||||
const ownerRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER);
|
||||
const viewerRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER);
|
||||
const editorRole = await db.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR);
|
||||
const ownerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_OWNER);
|
||||
const viewerRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_VIEWER);
|
||||
const editorRole = await methods.findRoleByIdentifier(AccessRoleIds.PROMPTGROUP_EDITOR);
|
||||
|
||||
if (!ownerRole || !viewerRole || !editorRole) {
|
||||
logger.warn(
|
||||
@@ -66,8 +96,8 @@ export async function checkPromptPermissionsMigration({
|
||||
};
|
||||
}
|
||||
|
||||
// Get global project prompt group IDs
|
||||
const globalProject = await db.getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']);
|
||||
/** Global project prompt group IDs */
|
||||
const globalProject = await methods.getProjectByName(GLOBAL_PROJECT_NAME, ['promptGroupIds']);
|
||||
const globalPromptGroupIds = new Set(
|
||||
(globalProject?.promptGroupIds || []).map((id) => id.toString()),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/client",
|
||||
"version": "0.2.6",
|
||||
"version": "0.2.7",
|
||||
"description": "React components for LibreChat",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
@@ -64,7 +64,9 @@
|
||||
"react-hook-form": "^7.56.4",
|
||||
"react-resizable-panels": "^3.0.2",
|
||||
"react-textarea-autosize": "^8.4.0",
|
||||
"tailwind-merge": "^1.9.1"
|
||||
"tailwind-merge": "^1.9.1",
|
||||
"@dicebear/core": "^9.2.2",
|
||||
"@dicebear/collection": "^9.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
|
||||
102
packages/client/src/components/Avatar.tsx
Normal file
102
packages/client/src/components/Avatar.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import type { TUser } from 'librechat-data-provider';
|
||||
import { Skeleton } from './Skeleton';
|
||||
import { useAvatar } from '~/hooks';
|
||||
import { UserIcon } from '~/svgs';
|
||||
|
||||
export interface AvatarProps {
|
||||
user?: TUser;
|
||||
size?: number;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
showDefaultWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
const Avatar: React.FC<AvatarProps> = ({
|
||||
user,
|
||||
size = 32,
|
||||
className = '',
|
||||
alt,
|
||||
showDefaultWhenEmpty = true,
|
||||
}) => {
|
||||
const avatarSrc = useAvatar(user);
|
||||
const [imageLoaded, setImageLoaded] = useState(false);
|
||||
const [imageError, setImageError] = useState(false);
|
||||
|
||||
const avatarSeed = useMemo(
|
||||
() => user?.avatar || user?.username || user?.email || '',
|
||||
[user?.avatar, user?.username, user?.email],
|
||||
);
|
||||
|
||||
const altText = useMemo(
|
||||
() => alt || `${user?.name || user?.username || user?.email || ''}'s avatar`,
|
||||
[alt, user?.name, user?.username, user?.email],
|
||||
);
|
||||
|
||||
const imageSrc = useMemo(() => {
|
||||
if (!avatarSeed || imageError) return '';
|
||||
return (user?.avatar ?? '') || avatarSrc || '';
|
||||
}, [user?.avatar, avatarSrc, avatarSeed, imageError]);
|
||||
|
||||
const handleImageLoad = useCallback(() => {
|
||||
setImageLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleImageError = useCallback(() => {
|
||||
setImageError(true);
|
||||
setImageLoaded(false);
|
||||
}, []);
|
||||
|
||||
const DefaultAvatar = useCallback(
|
||||
() => (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'rgb(121, 137, 255)',
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
boxShadow: 'rgba(240, 246, 252, 0.1) 0px 0px 0px 1px',
|
||||
}}
|
||||
className={`relative flex items-center justify-center rounded-full p-1 text-text-primary ${className}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<UserIcon />
|
||||
</div>
|
||||
),
|
||||
[size, className],
|
||||
);
|
||||
|
||||
if (avatarSeed.length === 0 && showDefaultWhenEmpty) {
|
||||
return <DefaultAvatar />;
|
||||
}
|
||||
|
||||
if (avatarSeed.length > 0 && !imageError) {
|
||||
return (
|
||||
<div className="relative" style={{ width: `${size}px`, height: `${size}px` }}>
|
||||
{!imageLoaded && (
|
||||
<Skeleton className="rounded-full" style={{ width: `${size}px`, height: `${size}px` }} />
|
||||
)}
|
||||
|
||||
<img
|
||||
style={{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
display: imageLoaded ? 'block' : 'none',
|
||||
}}
|
||||
className={`rounded-full ${className}`}
|
||||
src={imageSrc}
|
||||
alt={altText}
|
||||
onLoad={handleImageLoad}
|
||||
onError={handleImageError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (imageError && showDefaultWhenEmpty) {
|
||||
return <DefaultAvatar />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default Avatar;
|
||||
@@ -33,6 +33,7 @@ export * from './Resizable';
|
||||
export * from './Select';
|
||||
export { default as Radio } from './Radio';
|
||||
export { default as Badge } from './Badge';
|
||||
export { default as Avatar } from './Avatar';
|
||||
export { default as Combobox } from './Combobox';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
export { default as SplitText } from './SplitText';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
export type { TranslationKeys } from './useLocalize';
|
||||
|
||||
export { default as useToast } from './useToast';
|
||||
export { default as useAvatar } from './useAvatar';
|
||||
export { default as useCombobox } from './useCombobox';
|
||||
export { default as useLocalize } from './useLocalize';
|
||||
export { default as useMediaQuery } from './useMediaQuery';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.8.003",
|
||||
"version": "0.8.004",
|
||||
"description": "data services for librechat apps",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
|
||||
@@ -1520,7 +1520,7 @@ export enum TTSProviders {
|
||||
/** Enum for app-wide constants */
|
||||
export enum Constants {
|
||||
/** Key for the app's version. */
|
||||
VERSION = 'v0.8.0-rc2',
|
||||
VERSION = 'v0.8.0-rc3',
|
||||
/** Key for the Custom Config's version (librechat.yaml). */
|
||||
CONFIG_VERSION = '1.2.8',
|
||||
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
|
||||
|
||||
@@ -164,11 +164,12 @@ export type TCategory = {
|
||||
id?: string;
|
||||
value: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
custom?: boolean;
|
||||
};
|
||||
|
||||
export type TMarketplaceCategory = TCategory & {
|
||||
count: number;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type TError = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@librechat/data-schemas",
|
||||
"version": "0.0.19",
|
||||
"version": "0.0.20",
|
||||
"description": "Mongoose schemas and models for LibreChat",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
|
||||
@@ -51,7 +51,7 @@ const transports: winston.transport[] = [
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
format: fileFormat,
|
||||
format: winston.format.combine(fileFormat, winston.format.json()),
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Model, Types, DeleteResult } from 'mongoose';
|
||||
import type { IAgentCategory, AgentCategory } from '../types/agentCategory';
|
||||
import type { Model, Types } from 'mongoose';
|
||||
import type { IAgentCategory } from '~/types';
|
||||
|
||||
export function createAgentCategoryMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
@@ -52,8 +52,9 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
|
||||
label?: string;
|
||||
description?: string;
|
||||
order?: number;
|
||||
custom?: boolean;
|
||||
}>,
|
||||
): Promise<any> {
|
||||
): Promise<import('mongoose').mongo.BulkWriteResult> {
|
||||
const AgentCategory = mongoose.models.AgentCategory as Model<IAgentCategory>;
|
||||
|
||||
const operations = categories.map((category, index) => ({
|
||||
@@ -66,6 +67,7 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
|
||||
description: category.description || '',
|
||||
order: category.order || index,
|
||||
isActive: true,
|
||||
custom: category.custom || false,
|
||||
},
|
||||
},
|
||||
upsert: true,
|
||||
@@ -145,63 +147,104 @@ export function createAgentCategoryMethods(mongoose: typeof import('mongoose'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure default categories exist, seed them if none are present
|
||||
* @returns Promise<boolean> - true if categories were seeded, false if they already existed
|
||||
* Ensure default categories exist and update them if they don't have localization keys
|
||||
* @returns Promise<boolean> - true if categories were created/updated, false if no changes
|
||||
*/
|
||||
async function ensureDefaultCategories(): Promise<boolean> {
|
||||
const existingCategories = await getAllCategories();
|
||||
|
||||
if (existingCategories.length > 0) {
|
||||
return false; // Categories already exist
|
||||
}
|
||||
const AgentCategory = mongoose.models.AgentCategory as Model<IAgentCategory>;
|
||||
|
||||
const defaultCategories = [
|
||||
{
|
||||
value: 'general',
|
||||
label: 'General',
|
||||
description: 'General purpose agents for common tasks and inquiries',
|
||||
label: 'com_agents_category_general',
|
||||
description: 'com_agents_category_general_description',
|
||||
order: 0,
|
||||
},
|
||||
{
|
||||
value: 'hr',
|
||||
label: 'Human Resources',
|
||||
description: 'Agents specialized in HR processes, policies, and employee support',
|
||||
label: 'com_agents_category_hr',
|
||||
description: 'com_agents_category_hr_description',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
value: 'rd',
|
||||
label: 'Research & Development',
|
||||
description: 'Agents focused on R&D processes, innovation, and technical research',
|
||||
label: 'com_agents_category_rd',
|
||||
description: 'com_agents_category_rd_description',
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
value: 'finance',
|
||||
label: 'Finance',
|
||||
description: 'Agents specialized in financial analysis, budgeting, and accounting',
|
||||
label: 'com_agents_category_finance',
|
||||
description: 'com_agents_category_finance_description',
|
||||
order: 3,
|
||||
},
|
||||
{
|
||||
value: 'it',
|
||||
label: 'IT',
|
||||
description: 'Agents for IT support, technical troubleshooting, and system administration',
|
||||
label: 'com_agents_category_it',
|
||||
description: 'com_agents_category_it_description',
|
||||
order: 4,
|
||||
},
|
||||
{
|
||||
value: 'sales',
|
||||
label: 'Sales',
|
||||
description: 'Agents focused on sales processes, customer relations.',
|
||||
label: 'com_agents_category_sales',
|
||||
description: 'com_agents_category_sales_description',
|
||||
order: 5,
|
||||
},
|
||||
{
|
||||
value: 'aftersales',
|
||||
label: 'After Sales',
|
||||
description: 'Agents specialized in post-sale support, maintenance, and customer service',
|
||||
label: 'com_agents_category_aftersales',
|
||||
description: 'com_agents_category_aftersales_description',
|
||||
order: 6,
|
||||
},
|
||||
];
|
||||
|
||||
await seedCategories(defaultCategories);
|
||||
return true; // Categories were seeded
|
||||
const existingCategories = await getAllCategories();
|
||||
const existingCategoryMap = new Map(existingCategories.map((cat) => [cat.value, cat]));
|
||||
|
||||
const updates = [];
|
||||
let created = 0;
|
||||
|
||||
for (const defaultCategory of defaultCategories) {
|
||||
const existingCategory = existingCategoryMap.get(defaultCategory.value);
|
||||
|
||||
if (existingCategory) {
|
||||
const isNotCustom = !existingCategory.custom;
|
||||
const needsLocalization = !existingCategory.label.startsWith('com_');
|
||||
|
||||
if (isNotCustom && needsLocalization) {
|
||||
updates.push({
|
||||
value: defaultCategory.value,
|
||||
label: defaultCategory.label,
|
||||
description: defaultCategory.description,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await createCategory({
|
||||
...defaultCategory,
|
||||
isActive: true,
|
||||
custom: false,
|
||||
});
|
||||
created++;
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.length > 0) {
|
||||
const bulkOps = updates.map((update) => ({
|
||||
updateOne: {
|
||||
filter: { value: update.value, custom: { $ne: true } },
|
||||
update: {
|
||||
$set: {
|
||||
label: update.label,
|
||||
description: update.description,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
await AgentCategory.bulkWrite(bulkOps, { ordered: false });
|
||||
}
|
||||
|
||||
return updates.length > 0 || created > 0;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Schema, Document } from 'mongoose';
|
||||
import { Schema } from 'mongoose';
|
||||
import type { IAgentCategory } from '~/types';
|
||||
|
||||
const agentCategorySchema = new Schema<IAgentCategory>(
|
||||
@@ -31,6 +31,10 @@ const agentCategorySchema = new Schema<IAgentCategory>(
|
||||
default: true,
|
||||
index: true,
|
||||
},
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -11,6 +11,8 @@ export type AgentCategory = {
|
||||
order: number;
|
||||
/** Whether the category is active and should be displayed */
|
||||
isActive: boolean;
|
||||
/** Whether this is a custom user-created category */
|
||||
custom?: boolean;
|
||||
};
|
||||
|
||||
export type IAgentCategory = AgentCategory &
|
||||
|
||||
Reference in New Issue
Block a user