Concierge is an open-source web application that provides AI applications to enterprise customers for a company's internal use. Concierge encapsulates functions like document translation, copy editing, summarization, headline generation, tagging, entity extraction, etc. into easy-to-use task-specific interfaces rather than just making the functionality available through a chat-style prompting interface. Concierge is a one-stop shop for LLM-based AI functionality at our network. Concierge is built on top of Cortex - our open-source graphQL middle tier for AI.
The following environment variables are required to configure Concierge to connect to Cortex and the Media Helper app:
CORTEX_GRAPHQL_API_URL: the full GraphQL URL of the Cortex deployment, e.g.https://<site>.azure-api.net/graphql?subscription-key=<key>CORTEX_MEDIA_API_URL: the full URL of the Cortex media helper app, e.g.https://<site>.azure-api.net/media-helper?subscription-key=<key>
The following environment variable is optional and used for blue/green deployment scenarios:
CORTEX_GRAPHQL_API_BLUE_URL: When you want to test your application against a pre-production ("blue") environment while using the same build that will run in production, set this to the Cortex "blue" URL. This variable must be set both at build-time and at runtime in the blue environment only. This is particularly useful for testing new features or changes in a pre-production environment before deploying to production.
The following environment variable is needed to deliver user feedback to Slack:
SLACK_WEBHOOK_URL- the is the URL of the Slack webhook you've configured to deliver the message to whichever channel you want to deliver it to.
In the project directory, you can run:
Runs the app in the development mode.
Open http://localhost:3000 to view it in your browser.
The page will reload when you make changes.
You may also see any lint errors in the console.
Launches the test runner in the interactive watch mode.
See the section about running tests for more information.
Builds the app for production to the build folder.
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.
Your app is ready to be deployed!
See the section about deployment for more information.
The Concierge system includes a robust background task processing system that allows you to run time-consuming operations asynchronously. Here's how the offline task system works and how to define a new task.
Concierge uses a queuing system based on BullMQ to handle background tasks. Here's the general flow:
- Task Creation: When a task is initiated, a record is created in the database with a "pending" status
- Task Queueing: The task is added to a Redis-backed queue for processing
- Task Execution: A worker picks up the task and executes it
- Task Completion: Upon completion, the task status is updated in the database
- Client-Side Handling: React components can monitor task status and respond to completion
Tasks support both synchronous and asynchronous execution modes, with progress tracking, error handling, and configurable timeouts.
To create a new task type in Concierge, follow these steps:
Create a new file in the jobs/tasks/ directory. Each task handler should extend the BaseTask class:
import { BaseTask } from './base-task.mjs';
class NewTaskHandler extends BaseTask {
constructor() {
super();
}
// Required: Provide a human-readable name for the task
get displayName() {
return "New Task Type";
}
// Required: Implement the task execution logic
async startRequest(job) {
const { taskId, userId, metadata } = job.data;
try {
// Update task to in_progress
await this.updateTaskStatus(taskId, "in_progress");
// Your task implementation goes here
// ...
// Update progress as needed
await this.updateTaskProgress(taskId, 50, "Processing data...");
// Final result
const result = { /* your result data */ };
// Mark as completed
await this.updateTaskStatus(taskId, "completed", result);
return result;
} catch (error) {
// Handle errors
console.error("Task failed:", error);
await this.updateTaskStatus(taskId, "failed", null, error.message);
throw error;
}
}
// Optional: Custom completion handler
async handleCompletion(taskId, dataObject, infoObject, metadata, client) {
// Custom logic after task completes
// For example, update other data, send notifications, etc.
return dataObject;
}
}
export default new NewTaskHandler();If your task needs to trigger client-side actions when completed, add a handler to the clientSideCompletionHandlers object in queries/notifications.js:
// ... existing code ...
const clientSideCompletionHandlers = {
// ... existing handlers ...
"new-task-name": async ({ task, queryClient, refetchUserState }) => {
// Perform client-side actions after completion
// For example:
queryClient.invalidateQueries({ queryKey: ["some-related-data"] });
refetchUserState();
},
};
// ... existing code ...Concierge provides a built-in hook called useRunTask() for initiating background tasks. Use this hook in your components:
import { useRunTask } from "../../app/queries/notifications";
import { useNotificationsContext } from "../../src/contexts/NotificationContext";
function YourComponent() {
const runTask = useRunTask();
const { openNotifications } = useNotificationsContext();
const handleStartTask = async () => {
try {
// The type property identifies which task handler to use
const result = await runTask.mutateAsync({
type: "new-task-name",
// Your task parameters
parameter1: "value1",
parameter2: "value2",
// Optional: specify the source for tracking
source: "your_component",
});
// Optional: open notifications panel to show task progress
openNotifications();
return result;
} catch (error) {
console.error("Task failed:", error);
}
};
return (
<div>
<button onClick={handleStartTask} disabled={runTask.isPending}>
{runTask.isPending ? "Starting..." : "Start Task"}
</button>
</div>
);
}You can monitor the status of a task using the useTask hook:
import { useRunTask, useTask } from "../../app/queries/notifications";
function YourComponent() {
const runTask = useRunTask();
const [taskId, setTaskId] = useState(null);
const { data: taskStatus } = useTask(taskId);
const handleStartTask = async () => {
const result = await runTask.mutateAsync({
type: "new-task-name",
// Task parameters...
});
setTaskId(result.taskId);
};
return (
<div>
<button onClick={handleStartTask}>Start Task</button>
{taskStatus && (
<div>
Status: {taskStatus.status}
{taskStatus.statusText && <p>{taskStatus.statusText}</p>}
{taskStatus.progress > 0 && (
<progress value={taskStatus.progress} max="100" />
)}
</div>
)}
</div>
);
}- Keep Tasks Focused: Each task should do one thing well
- Handle Errors Gracefully: Always update task status on errors
- Provide Progress Updates: When possible, update progress periodically
- Timeout Handling: Set appropriate timeouts for long-running tasks
- Clean Up Resources: Release any resources in the completion handler
The task system in Concierge provides a flexible way to handle background processing while maintaining a responsive user experience.
- Node.js (version specified in package.json)
- npm
- Redis (required for BullMQ task processing)
- Clone the repository
- Install dependencies:
npm installThe project includes several npm scripts for development:
npm run dev: Runs both the Next.js development server and the worker process concurrentlynpm run next:dev: Runs only the Next.js development servernpm run worker:dev: Runs only the worker process with hot reloading using nodemonnpm run lint: Runs ESLint and Prettier checksnpm run format: Formats code using Prettiernpm test: Runs Jest tests
- Start the development servers:
npm run devThis will start:
- Next.js dev server at http://localhost:3000
- Worker process for background task processing
- For development of specific components:
- Web app only:
npm run next:dev - Worker only:
npm run worker:dev
- Web app only:
The Concierge application includes a comprehensive admin section that provides administrative tools and monitoring capabilities. This section is only accessible to users with the admin role.
The admin section is protected by role-based access control:
- Only users with the
adminrole can access the admin section - Unauthorized users are automatically redirected to the home page
- Admin users cannot modify their own role to prevent accidental self-demotion
The user management interface allows administrators to:
- View all users in the system
- Search users by name or username
- Modify user roles (between 'user' and 'admin')
- Paginate through user listings
- View user details including:
- Name
- Username
- Current role
- Account status
The queue monitoring dashboard provides real-time insights into the background task processing system:
- View statistics for different queues
- Monitor active jobs with progress tracking
- View failed jobs with error details
- Perform queue management actions
- Real-time updates (refreshes every 5 seconds)
Key metrics displayed:
- Active jobs count
- Failed jobs count
- Job progress
- Error messages
- Timestamps
To add new admin features:
- Create new components in the
app/admindirectory - Add new routes in the
app/apidirectory with proper admin role checks - Update the
AdminNavcomponent to include new navigation items - Implement proper access control using the
getCurrentUserutility
When developing admin features:
- Always verify admin role using
getCurrentUser() - Implement proper error handling
- Use appropriate HTTP status codes for unauthorized access
- Validate all user inputs
- Log important administrative actions