Compare commits
4 Commits
main
...
copilot/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c31309a1db | ||
|
|
3c4c4d53dd | ||
|
|
9fe4554a70 | ||
|
|
c1dcabae20 |
@@ -327,7 +327,7 @@ const loadTools = async ({
|
||||
const { onSearchResults, onGetHighlights } = options?.[Tools.web_search] ?? {};
|
||||
requestedTools[tool] = async () => {
|
||||
toolContextMap[tool] = `# \`${tool}\`:
|
||||
Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}
|
||||
Current Date & Time: ${replaceSpecialVars({ text: '{{iso_datetime}}' })}${options.req?.body?.timezone ? `\nLocal Date & Time: ${replaceSpecialVars({ text: '{{local_datetime}}', timezone: options.req.body.timezone })}` : ''}
|
||||
1. **Execute immediately without preface** when using \`${tool}\`.
|
||||
2. **After the search, begin with a brief summary** that directly addresses the query without headers or explaining your process.
|
||||
3. **Structure your response clearly** using Markdown formatting (Level 2 headers for sections, lists for multiple points, tables for comparisons).
|
||||
|
||||
@@ -185,6 +185,7 @@ const initializeAgent = async ({
|
||||
agent.instructions = replaceSpecialVars({
|
||||
text: agent.instructions,
|
||||
user: req.user,
|
||||
timezone: req.body?.timezone,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ export default function VariableForm({
|
||||
|
||||
const mainText = useMemo(() => {
|
||||
const initialText = group.productionPrompt?.prompt ?? '';
|
||||
return replaceSpecialVars({ text: initialText, user });
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
return replaceSpecialVars({ text: initialText, user, timezone });
|
||||
}, [group.productionPrompt?.prompt, user]);
|
||||
|
||||
const { allVariables, uniqueVariables, variableIndexMap } = useMemo(
|
||||
|
||||
@@ -22,7 +22,8 @@ const PromptDetails = ({ group }: { group?: TPromptGroup }) => {
|
||||
|
||||
const mainText = useMemo(() => {
|
||||
const initialText = group?.productionPrompt?.prompt ?? '';
|
||||
return replaceSpecialVars({ text: initialText, user });
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
return replaceSpecialVars({ text: initialText, user, timezone });
|
||||
}, [group?.productionPrompt?.prompt, user]);
|
||||
|
||||
if (!group) {
|
||||
|
||||
@@ -121,9 +121,11 @@ export default function useChatFunctions({
|
||||
let currentMessages: TMessage[] | null = overrideMessages ?? getMessages() ?? [];
|
||||
|
||||
if (conversation?.promptPrefix) {
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
conversation.promptPrefix = replaceSpecialVars({
|
||||
text: conversation.promptPrefix,
|
||||
user,
|
||||
timezone,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,8 @@ export default function useSubmitMessage() {
|
||||
|
||||
const submitPrompt = useCallback(
|
||||
(text: string) => {
|
||||
const parsedText = replaceSpecialVars({ text, user });
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const parsedText = replaceSpecialVars({ text, user, timezone });
|
||||
if (autoSendPrompts) {
|
||||
submitMessage({ text: parsedText });
|
||||
return;
|
||||
|
||||
114
packages/data-provider/SPECIAL_VARIABLES.md
Normal file
114
packages/data-provider/SPECIAL_VARIABLES.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Special Variables in LibreChat
|
||||
|
||||
LibreChat supports special variables that can be used in agent prompts, conversation prompts, and other text fields. These variables are automatically replaced with their corresponding values at runtime.
|
||||
|
||||
## Available Special Variables
|
||||
|
||||
### Date and Time Variables
|
||||
|
||||
#### UTC-based Variables
|
||||
- **`{{current_date}}`**: Current date in UTC timezone
|
||||
- Format: `YYYY-MM-DD (D)` where D is the day of week (0=Sunday, 1=Monday, etc.)
|
||||
- Example: `2024-04-29 (1)` (Monday, April 29, 2024)
|
||||
|
||||
- **`{{current_datetime}}`**: Current date and time in UTC timezone
|
||||
- Format: `YYYY-MM-DD HH:mm:ss (D)` where D is the day of week
|
||||
- Example: `2024-04-29 12:34:56 (1)` (Monday, April 29, 2024 at 12:34:56 PM UTC)
|
||||
|
||||
- **`{{iso_datetime}}`**: Current date and time in ISO 8601 format (UTC)
|
||||
- Format: ISO 8601 standard
|
||||
- Example: `2024-04-29T16:34:56.000Z`
|
||||
|
||||
#### Local Timezone Variables
|
||||
- **`{{local_date}}`**: Current date in the user's local timezone
|
||||
- Format: `YYYY-MM-DD (D)` where D is the day of week
|
||||
- Example: `2024-04-29 (1)` (Monday, April 29, 2024 in user's timezone)
|
||||
- Note: Falls back to UTC if timezone is not available
|
||||
|
||||
- **`{{local_datetime}}`**: Current date and time in the user's local timezone
|
||||
- Format: `YYYY-MM-DD HH:mm:ss (D)` where D is the day of week
|
||||
- Example: `2024-04-29 08:34:56 (1)` (Monday, April 29, 2024 at 8:34:56 AM in America/New_York)
|
||||
- Note: Falls back to UTC if timezone is not available
|
||||
|
||||
### User Variables
|
||||
- **`{{current_user}}`**: Name of the current user
|
||||
- Example: `John Doe`
|
||||
- Note: Only replaced if user information is available
|
||||
|
||||
## Day of Week Reference
|
||||
- `0` = Sunday
|
||||
- `1` = Monday
|
||||
- `2` = Tuesday
|
||||
- `3` = Wednesday
|
||||
- `4` = Thursday
|
||||
- `5` = Friday
|
||||
- `6` = Saturday
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Agent Instructions
|
||||
```
|
||||
You are an AI assistant helping {{current_user}}.
|
||||
Today's date is {{local_date}}.
|
||||
Current time in user's timezone: {{local_datetime}}
|
||||
UTC time: {{current_datetime}}
|
||||
```
|
||||
|
||||
### System Prompts
|
||||
```
|
||||
System time (UTC): {{current_datetime}}
|
||||
User's local time: {{local_datetime}}
|
||||
Remember to consider the user's timezone when scheduling or discussing times.
|
||||
```
|
||||
|
||||
### Conversation Starters
|
||||
```
|
||||
Good morning {{current_user}}! Today is {{local_date}}.
|
||||
How can I help you today?
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Timezone Detection
|
||||
The user's timezone is automatically detected from their browser using the JavaScript `Intl.DateTimeFormat().resolvedOptions().timeZone` API. This provides an IANA timezone identifier (e.g., `America/New_York`, `Europe/London`, `Asia/Tokyo`).
|
||||
|
||||
### Fallback Behavior
|
||||
If the timezone cannot be detected or is invalid:
|
||||
- `{{local_date}}` falls back to `{{current_date}}` (UTC)
|
||||
- `{{local_datetime}}` falls back to `{{current_datetime}}` (UTC)
|
||||
|
||||
### Case Insensitivity
|
||||
All special variables are case-insensitive. The following are all equivalent:
|
||||
- `{{current_date}}` = `{{Current_Date}}` = `{{CURRENT_DATE}}`
|
||||
- `{{local_datetime}}` = `{{Local_DateTime}}` = `{{LOCAL_DATETIME}}`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
For developers working with special variables:
|
||||
|
||||
1. **Server-side processing**: The `replaceSpecialVars` function in `packages/data-provider/src/parsers.ts` handles all replacements.
|
||||
|
||||
2. **Timezone propagation**: The timezone is automatically included in API requests from the client.
|
||||
|
||||
3. **Testing**: Comprehensive tests are available in `packages/data-provider/specs/parsers.spec.ts`.
|
||||
|
||||
4. **Dependencies**: The implementation uses `dayjs` with `timezone` and `utc` plugins for timezone conversions.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you were using the old UTC-only variables in your prompts, they continue to work exactly as before. The new local timezone variables are additive and don't break existing functionality.
|
||||
|
||||
### Upgrading Existing Prompts
|
||||
```diff
|
||||
- Current time (UTC): {{current_datetime}}
|
||||
+ Current time (UTC): {{current_datetime}}
|
||||
+ Current time (Your timezone): {{local_datetime}}
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential future additions:
|
||||
- Custom date/time formats
|
||||
- Relative time expressions (e.g., "tomorrow", "next week")
|
||||
- User-specified timezone overrides
|
||||
- Additional locale-aware formatting options
|
||||
@@ -5,7 +5,7 @@ import type { TUser } from '../src/types';
|
||||
// Mock dayjs module with consistent date/time values regardless of environment
|
||||
jest.mock('dayjs', () => {
|
||||
// Create a mock implementation that returns fixed values
|
||||
const mockDayjs = () => ({
|
||||
const mockDayjs = (input?: any) => ({
|
||||
format: (format: string) => {
|
||||
if (format === 'YYYY-MM-DD') {
|
||||
return '2024-04-29';
|
||||
@@ -17,6 +17,33 @@ jest.mock('dayjs', () => {
|
||||
},
|
||||
day: () => 1, // 1 = Monday
|
||||
toISOString: () => '2024-04-29T16:34:56.000Z',
|
||||
tz: (timezone: string) => ({
|
||||
format: (format: string) => {
|
||||
// Mock timezone-specific formatting for America/New_York (UTC-4 during DST)
|
||||
if (timezone === 'America/New_York') {
|
||||
if (format === 'YYYY-MM-DD') {
|
||||
return '2024-04-29';
|
||||
}
|
||||
if (format === 'YYYY-MM-DD HH:mm:ss') {
|
||||
return '2024-04-29 08:34:56'; // 4 hours behind UTC 12:34:56
|
||||
}
|
||||
}
|
||||
// Mock timezone-specific formatting for Asia/Tokyo (UTC+9)
|
||||
if (timezone === 'Asia/Tokyo') {
|
||||
if (format === 'YYYY-MM-DD') {
|
||||
return '2024-04-29';
|
||||
}
|
||||
if (format === 'YYYY-MM-DD HH:mm:ss') {
|
||||
return '2024-04-29 21:34:56'; // 9 hours ahead of UTC 12:34:56
|
||||
}
|
||||
}
|
||||
return format;
|
||||
},
|
||||
day: () => {
|
||||
// Return same day number for simplicity in tests
|
||||
return 1;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Add any static methods needed
|
||||
@@ -121,5 +148,55 @@ describe('replaceSpecialVars', () => {
|
||||
expect(result).toContain('2024-04-29 12:34:56 (1)'); // current_datetime
|
||||
expect(result).toContain('2024-04-29T16:34:56.000Z'); // iso_datetime
|
||||
expect(result).toContain('Test User'); // current_user
|
||||
// local_date and local_datetime should fall back to UTC when no timezone provided
|
||||
expect(result).toContain('2024-04-29 (1)'); // local_date (fallback to UTC)
|
||||
expect(result).toContain('2024-04-29 12:34:56 (1)'); // local_datetime (fallback to UTC)
|
||||
});
|
||||
|
||||
test('should replace {{local_date}} with the timezone-aware date when timezone is provided', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'Today in NY is {{local_date}}',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
expect(result).toBe('Today in NY is 2024-04-29 (1)');
|
||||
});
|
||||
|
||||
test('should replace {{local_datetime}} with the timezone-aware datetime when timezone is provided', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'Now in NY is {{local_datetime}}',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
expect(result).toBe('Now in NY is 2024-04-29 08:34:56 (1)');
|
||||
});
|
||||
|
||||
test('should replace {{local_datetime}} with Tokyo timezone', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'Now in Tokyo is {{local_datetime}}',
|
||||
timezone: 'Asia/Tokyo',
|
||||
});
|
||||
expect(result).toBe('Now in Tokyo is 2024-04-29 21:34:56 (1)');
|
||||
});
|
||||
|
||||
test('should fall back to UTC for local variables when no timezone is provided', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'Date: {{local_date}}, Time: {{local_datetime}}',
|
||||
});
|
||||
expect(result).toBe('Date: 2024-04-29 (1), Time: 2024-04-29 12:34:56 (1)');
|
||||
});
|
||||
|
||||
test('should handle both UTC and local timezone variables in the same text', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'UTC: {{current_datetime}}, Local: {{local_datetime}}',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
expect(result).toBe('UTC: 2024-04-29 12:34:56 (1), Local: 2024-04-29 08:34:56 (1)');
|
||||
});
|
||||
|
||||
test('should be case-insensitive for local timezone variables', () => {
|
||||
const result = replaceSpecialVars({
|
||||
text: 'Date: {{LOCAL_DATE}}, Time: {{Local_DateTime}}',
|
||||
timezone: 'America/New_York',
|
||||
});
|
||||
expect(result).toBe('Date: 2024-04-29 (1), Time: 2024-04-29 08:34:56 (1)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1723,6 +1723,8 @@ export const specialVariables = {
|
||||
current_user: true,
|
||||
iso_datetime: true,
|
||||
current_datetime: true,
|
||||
local_date: true,
|
||||
local_datetime: true,
|
||||
};
|
||||
|
||||
export type TSpecialVarLabel = `com_ui_special_var_${keyof typeof specialVariables}`;
|
||||
|
||||
@@ -38,6 +38,8 @@ export default function createPayload(submission: t.TSubmission) {
|
||||
conversationId,
|
||||
isContinued: !!(isEdited && isContinued),
|
||||
ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent,
|
||||
timezone:
|
||||
typeof Intl !== 'undefined' ? Intl.DateTimeFormat().resolvedOptions().timeZone : undefined,
|
||||
};
|
||||
|
||||
return { server, payload };
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import type { ZodIssue } from 'zod';
|
||||
import type * as a from './types/assistants';
|
||||
import type * as s from './schemas';
|
||||
import type * as t from './types';
|
||||
import { ContentTypes } from './types/runs';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
import {
|
||||
openAISchema,
|
||||
googleSchema,
|
||||
@@ -410,7 +415,46 @@ export function findLastSeparatorIndex(text: string, separators = SEPARATORS): n
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
export function replaceSpecialVars({ text, user }: { text: string; user?: t.TUser | null }) {
|
||||
/**
|
||||
* Replaces special variables in text with their corresponding values.
|
||||
*
|
||||
* Available special variables:
|
||||
* - {{current_date}}: Current date in UTC (YYYY-MM-DD with day of week)
|
||||
* - {{current_datetime}}: Current datetime in UTC (YYYY-MM-DD HH:mm:ss with day of week)
|
||||
* - {{iso_datetime}}: Current datetime in ISO 8601 format (UTC)
|
||||
* - {{local_date}}: Current date in user's local timezone (YYYY-MM-DD with day of week)
|
||||
* - {{local_datetime}}: Current datetime in user's local timezone (YYYY-MM-DD HH:mm:ss with day of week)
|
||||
* - {{current_user}}: Name of the current user (if available)
|
||||
*
|
||||
* Day of week values: 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday
|
||||
*
|
||||
* Note: {{local_date}} and {{local_datetime}} require the timezone parameter.
|
||||
* If timezone is not provided, they will fall back to UTC values.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* replaceSpecialVars({
|
||||
* text: 'Today is {{local_date}} and it\'s {{current_user}}\'s session',
|
||||
* user: { name: 'John' },
|
||||
* timezone: 'America/New_York'
|
||||
* });
|
||||
* // Result: "Today is 2024-04-29 (1) and it's John's session"
|
||||
* ```
|
||||
*
|
||||
* @param text - The text containing special variables to replace
|
||||
* @param user - Optional user object containing user information
|
||||
* @param timezone - Optional IANA timezone string (e.g., 'America/New_York', 'Europe/London', 'Asia/Tokyo')
|
||||
* @returns The text with all special variables replaced with their values
|
||||
*/
|
||||
export function replaceSpecialVars({
|
||||
text,
|
||||
user,
|
||||
timezone,
|
||||
}: {
|
||||
text: string;
|
||||
user?: t.TUser | null;
|
||||
timezone?: string;
|
||||
}) {
|
||||
let result = text;
|
||||
if (!result) {
|
||||
return result;
|
||||
@@ -428,6 +472,27 @@ export function replaceSpecialVars({ text, user }: { text: string; user?: t.TUse
|
||||
const isoDatetime = dayjs().toISOString();
|
||||
result = result.replace(/{{iso_datetime}}/gi, isoDatetime);
|
||||
|
||||
// Local timezone support
|
||||
if (timezone) {
|
||||
try {
|
||||
const localDate = dayjs().tz(timezone).format('YYYY-MM-DD');
|
||||
const localDayNumber = dayjs().tz(timezone).day();
|
||||
const localCombinedDate = `${localDate} (${localDayNumber})`;
|
||||
result = result.replace(/{{local_date}}/gi, localCombinedDate);
|
||||
|
||||
const localDatetime = dayjs().tz(timezone).format('YYYY-MM-DD HH:mm:ss');
|
||||
result = result.replace(/{{local_datetime}}/gi, `${localDatetime} (${localDayNumber})`);
|
||||
} catch {
|
||||
// If timezone is invalid, fall back to UTC values for local_* variables
|
||||
result = result.replace(/{{local_date}}/gi, combinedDate);
|
||||
result = result.replace(/{{local_datetime}}/gi, `${currentDatetime} (${dayNumber})`);
|
||||
}
|
||||
} else {
|
||||
// If no timezone is provided, replace local_* variables with UTC values
|
||||
result = result.replace(/{{local_date}}/gi, combinedDate);
|
||||
result = result.replace(/{{local_datetime}}/gi, `${currentDatetime} (${dayNumber})`);
|
||||
}
|
||||
|
||||
if (user && user.name) {
|
||||
result = result.replace(/{{current_user}}/gi, user.name);
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ export type TPayload = Partial<TMessage> &
|
||||
isTemporary: boolean;
|
||||
ephemeralAgent?: TEphemeralAgent | null;
|
||||
editedContent?: TEditedContent | null;
|
||||
timezone?: string;
|
||||
};
|
||||
|
||||
export type TEditedContent =
|
||||
|
||||
Reference in New Issue
Block a user