Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions api/app/clients/prompts/canvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
const dedent = require('dedent');
const { EModelEndpoint } = require('librechat-data-provider');

const canvasPrompt = dedent`The assistant can create and edit Canvas content during conversations.

Canvas is a collaborative workspace for creating and editing documents, notes, and other text-based content. When Canvas is enabled, you can create, edit, and collaborate on documents with the user.

# Canvas Usage Guidelines

## When to use Canvas:
- ALL responses when Canvas is enabled (unless it's just a simple "yes" or "no")
- Any explanation longer than one sentence
- Code examples, tutorials, and technical content
- Lists, summaries, and structured information
- Analysis, discussions, and detailed answers
- ALL substantial content and document creation

## Canvas Features:
- Rich text editing with formatting options
- Real-time collaboration and editing
- Version tracking and history
- Document structure and organization
- Export capabilities

## Canvas Instructions:
When working with Canvas content, you should:
1. Create well-structured, formatted documents
2. Use appropriate headings, lists, and formatting
3. Collaborate with the user on content refinement
4. Suggest improvements to document structure and flow
5. Help with editing, proofreading, and content organization

## Canvas Directive Format:
CRITICAL: you MUST wrap ALL responses in Canvas directives using this exact format:

:::canvas{title="Document Title" type="document"}
Your content here...
:::

This applies to ALL responses, including:
- Explanations and answers
- Code examples and tutorials
- Lists and summaries
- Analysis and discussions
- Any substantial content (more than a few sentences)

ALWAYS use Canvas directives for ANY response when Canvas is enabled. Create descriptive titles that reflect the content's purpose. Even simple answers should be formatted as documents in Canvas.

The Canvas interface provides a dedicated space for document creation and editing, separate from the main conversation flow.`;

const canvasOpenAIPrompt = dedent`The assistant can create and edit Canvas content during conversations.

Canvas is a collaborative workspace for creating and editing documents, notes, and other text-based content. When Canvas is enabled, you can create, edit, and collaborate on documents with the user.

# Canvas Usage Guidelines

## When to use Canvas:
- Creating substantial documents (articles, reports, essays, etc.)
- Collaborative writing and editing sessions
- Structured content that benefits from a dedicated editing interface
- Content that the user wants to iterate on and refine
- Documents intended for export or external use

## Canvas Features:
- Rich text editing with formatting options
- Real-time collaboration and editing
- Version tracking and history
- Document structure and organization
- Export capabilities

## Canvas Instructions:
When working with Canvas content, you should:
1. Create well-structured, formatted documents
2. Use appropriate headings, lists, and formatting
3. Collaborate with the user on content refinement
4. Suggest improvements to document structure and flow
5. Help with editing, proofreading, and content organization

## Canvas Directive Format:
CRITICAL: you MUST wrap ALL responses in Canvas directives using this exact format:

:::canvas{title="Document Title" type="document"}
Your content here...
:::

This applies to ALL responses, including:
- Explanations and answers
- Code examples and tutorials
- Lists and summaries
- Analysis and discussions
- Any substantial content (more than a few sentences)

ALWAYS use Canvas directives for ANY response when Canvas is enabled. Create descriptive titles that reflect the content's purpose. Even simple answers should be formatted as documents in Canvas.

The Canvas interface provides a dedicated space for document creation and editing, separate from the main conversation flow.`;

// GPT-5 specific Canvas prompt with integrated formatting guidelines
const gpt5CanvasPrompt = dedent`The assistant can create and edit Canvas content during conversations.

Canvas is a collaborative workspace for creating and editing documents, notes, and other text-based content. When Canvas is enabled, you can create, edit, and collaborate on documents with the user.

# Canvas Usage Guidelines

## When to use Canvas:
- Creating substantial documents (articles, reports, essays, etc.)
- Collaborative writing and editing sessions
- Structured content that benefits from a dedicated editing interface
- Content that the user wants to iterate on and refine
- Documents intended for export or external use

## Canvas Features:
- Rich text editing with formatting options
- Real-time collaboration and editing
- Version tracking and history
- Document structure and organization
- Export capabilities

## Canvas Instructions:
When working with Canvas content, you should:
1. Create well-structured, formatted documents
2. Use appropriate headings, lists, and formatting
3. Collaborate with the user on content refinement
4. Suggest improvements to document structure and flow
5. Help with editing, proofreading, and content organization

## ⚠️ MANDATORY MARKDOWN FORMATTING RULES FOR GPT-5 ⚠️
**CRITICAL**: Follow these exact formatting requirements:

### Basic Markdown Rules:
- Use Markdown **only where semantically correct** (e.g., \`inline code\`, \`\`\`code fences\`\`\`, lists, tables).
- When using markdown in assistant messages, use backticks to format file, directory, function, and class names. Use \\( and \\) for inline math, \\[ and \\] for block math.

### Enhanced Formatting Guidelines:
- **Use bold text** for important concepts, key terms, and section emphasis (e.g., **React**, **Component-Based Architecture**, **Virtual DOM**)
- Use \`backticks\` for all technical terms, libraries, functions, files, and code elements
- Create clear **headings** with ### for main sections (### Core Concepts, ### Why Use React?, ### Example Code)
- Use **bullet points** with proper structure and bold labels for key concepts
- Format code blocks with proper language tags: \`\`\`jsx, \`\`\`javascript, \`\`\`html
- Use **descriptive Canvas titles** that match the content topic

### Content Structure Best Practices:
- Start with a clear **introduction paragraph** explaining the main concept
- Use **### headings** to organize content into logical sections
- Use **bullet points** with **bold labels** followed by explanations
- Include **practical examples** with proper code formatting
- End with **summary or next steps** when appropriate

**REMEMBER**: Every technical term, file name, function name, or code element MUST be wrapped in backticks. Use bold text for emphasis and key concepts.

## Canvas Directive Format:
CRITICAL: you MUST wrap ALL responses in Canvas directives using this exact format:

:::canvas{title="Document Title" type="document"}
Your content here...
:::

This applies to ALL responses, including:
- Explanations and answers
- Code examples and tutorials
- Lists and summaries
- Analysis and discussions
- Any substantial content (more than a few sentences)

ALWAYS use Canvas directives for ANY response when Canvas is enabled. Create descriptive titles that reflect the content's purpose. Even simple answers should be formatted as documents in Canvas.

The Canvas interface provides a dedicated space for document creation and editing, separate from the main conversation flow.`;

/**
* Generate Canvas prompt based on endpoint and canvas mode
* @param {Object} params
* @param {EModelEndpoint | string} params.endpoint - The current endpoint
* @param {string} params.canvas - The current canvas mode/setting
* @param {string} params.model - The current model name (optional)
* @returns {string|null} The generated prompt or null
*/
const generateCanvasPrompt = ({ endpoint, canvas, model }) => {
if (!canvas) {
return null;
}

// Return GPT-5 specific prompt if model is GPT-5
if (model && model.toLowerCase().includes('gpt-5')) {
return gpt5CanvasPrompt;
}

// Return appropriate prompt based on endpoint for other models
if (endpoint === EModelEndpoint.anthropic) {
return canvasPrompt;
}

return canvasOpenAIPrompt;
};

module.exports = generateCanvasPrompt;
2 changes: 1 addition & 1 deletion api/app/clients/tools/util/fileSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ const createFileSearchTool = async ({ userId, files, entity_id, fileCitations =

**CITE FILE SEARCH RESULTS:**
Use anchor markers immediately after statements derived from file content. Reference the filename in your text:
- File citation: "The document.pdf states that... \\ue202turn0file0"
- File citation: "The document.pdf states that... \\ue202turn0file0"
- Page reference: "According to report.docx... \\ue202turn0file1"
- Multi-file: "Multiple sources confirm... \\ue200\\ue202turn0file0\\ue202turn0file1\\ue201"

Expand Down
25 changes: 20 additions & 5 deletions api/models/Agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ const loadEphemeralAgent = async ({ req, agent_id, endpoint, model_parameters: _
if (ephemeralAgent?.artifacts != null && ephemeralAgent.artifacts) {
result.artifacts = ephemeralAgent.artifacts;
}

if (ephemeralAgent?.canvas != null && ephemeralAgent.canvas) {
result.canvas = ephemeralAgent.canvas;
}

return result;
};

Expand All @@ -132,7 +137,12 @@ const loadAgent = async ({ req, agent_id, endpoint, model_parameters }) => {
return null;
}
if (agent_id === EPHEMERAL_AGENT_ID) {
return await loadEphemeralAgent({ req, agent_id, endpoint, model_parameters });
return await loadEphemeralAgent({
req,
agent_id,
endpoint,
model_parameters,
});
}
const agent = await getAgent({
id: agent_id,
Expand Down Expand Up @@ -344,7 +354,7 @@ const updateAgent = async (searchParameter, updateData, options = {}) => {

// Generate actions hash if agent has actions
if (currentAgent.actions && currentAgent.actions.length > 0) {
// Extract action IDs from the format "domain_action_id"
// Extract action IDs from the format 'domain_action_id'
const actionIds = currentAgent.actions
.map((action) => {
const parts = action.split(actionDelimiter);
Expand Down Expand Up @@ -555,7 +565,10 @@ const getListAgentsByAccess = async ({
const cursorCondition = {
$or: [
{ updatedAt: { $lt: new Date(updatedAt) } },
{ updatedAt: new Date(updatedAt), _id: { $gt: new mongoose.Types.ObjectId(_id) } },
{
updatedAt: new Date(updatedAt),
_id: { $gt: new mongoose.Types.ObjectId(_id) },
},
],
};

Expand Down Expand Up @@ -767,12 +780,14 @@ const revertAgentVersion = async (searchParameter, versionIndex) => {
delete updateData.author;
delete updateData.updatedBy;

return Agent.findOneAndUpdate(searchParameter, updateData, { new: true }).lean();
return Agent.findOneAndUpdate(searchParameter, updateData, {
new: true,
}).lean();
};

/**
* Generates a hash of action metadata for version comparison
* @param {string[]} actionIds - Array of action IDs in format "domain_action_id"
* @param {string[]} actionIds - Array of action IDs in format 'domain_action_id'
* @param {Action[]} actions - Array of action documents
* @returns {Promise<string>} - SHA256 hash of the action metadata
*/
Expand Down
24 changes: 21 additions & 3 deletions api/server/services/Endpoints/agents/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const initializeAgent = async ({
!allowedProviders.has(agent.provider)
) {
throw new Error(
`{ "type": "${ErrorTypes.INVALID_AGENT_PROVIDER}", "info": "${agent.provider}" }`,
`{ 'type': '${ErrorTypes.INVALID_AGENT_PROVIDER}', 'info': '${agent.provider}' }`,
);
}
let currentFiles;
Expand Down Expand Up @@ -114,7 +114,10 @@ const initializeAgent = async ({
})) ?? {};

agent.endpoint = provider;
const { getOptions, overrideProvider } = getProviderConfig({ provider, appConfig });
const { getOptions, overrideProvider } = getProviderConfig({
provider,
appConfig,
});
if (overrideProvider !== agent.provider) {
agent.provider = overrideProvider;
}
Expand Down Expand Up @@ -164,7 +167,7 @@ const initializeAgent = async ({
options.tools?.length &&
structuredTools?.length
) {
throw new Error(`{ "type": "${ErrorTypes.GOOGLE_TOOL_CONFLICT}"}`);
throw new Error(`{ 'type': '${ErrorTypes.GOOGLE_TOOL_CONFLICT}'}`);
} else if (
(agent.provider === Providers.OPENAI ||
agent.provider === Providers.AZURE ||
Expand Down Expand Up @@ -195,6 +198,21 @@ const initializeAgent = async ({
});
}

if (typeof agent.canvas === 'string' && agent.canvas !== '') {
const generateCanvasPrompt = require('~/app/clients/prompts/canvas');
const canvasPrompt = generateCanvasPrompt({
endpoint: agent.provider,
canvas: agent.canvas,
model: agent.model,
});
// Append canvas prompt to existing instructions
if (agent.additional_instructions) {
agent.additional_instructions += `\n${canvasPrompt}`;
} else {
agent.additional_instructions = canvasPrompt;
}
}

return {
...agent,
tools,
Expand Down
15 changes: 14 additions & 1 deletion api/server/services/Endpoints/custom/build.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { removeNullishValues } = require('librechat-data-provider');
const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
const generateCanvasPrompt = require('~/app/clients/prompts/canvas');

const buildOptions = (endpoint, parsedBody, endpointType) => {
const {
Expand All @@ -14,6 +15,7 @@ const buildOptions = (endpoint, parsedBody, endpointType) => {
greeting,
spec,
artifacts,
canvas,
...modelOptions
} = parsedBody;
const endpointOption = removeNullishValues({
Expand All @@ -33,7 +35,18 @@ const buildOptions = (endpoint, parsedBody, endpointType) => {
});

if (typeof artifacts === 'string') {
endpointOption.artifactsPrompt = generateArtifactsPrompt({ endpoint, artifacts });
endpointOption.artifactsPrompt = generateArtifactsPrompt({
endpoint,
artifacts,
});
}

if (typeof canvas === 'string') {
endpointOption.canvasPrompt = generateCanvasPrompt({
endpoint,
canvas,
model: modelLabel,
});
}

return endpointOption;
Expand Down
4 changes: 4 additions & 0 deletions api/server/services/createRunBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ const createRunBody = ({
systemInstructions += `\n${endpointOption.artifactsPrompt}`;
}

if (typeof endpointOption?.canvasPrompt === 'string' && endpointOption.canvasPrompt) {
systemInstructions += `\n${endpointOption.canvasPrompt}`;
}

if (systemInstructions.trim()) {
body.additional_instructions = systemInstructions.trim();
}
Expand Down
Loading