/** * * Copyright 2023-2025 InspectorRAGet Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **/ 'use client'; import { isEmpty } from 'lodash'; import { useMemo, useState, useEffect } from 'react'; import { Tag, Tooltip } from '@carbon/react'; import { AddComment } from '@carbon/icons-react'; import { Model, TaskCommentProvenance, TaskEvaluation } from '@/src/types'; import { useDataStore } from '@/src/store'; import { extractMouseSelection } from '@/src/utilities/selectors'; import { useNotification } from '@/src/components/notification/Notification'; import TaskTile from '@/src/components/task-tile/TaskTile'; import AddCommentModal from '@/src/components/comments/AddCommentModal'; import ViewComments from '@/src/components/comments/CommentsViewer'; import RAGTask from '@/src/views/task/RAGTask'; import TextGenerationTask from '@/src/views/task/TextGenerationTask'; import ChatTask from '@/src/views/task/ChatTask'; import classes from './Task.module.scss'; // =================================================================================== // TYPES // =================================================================================== interface Props { taskId: string; onClose: Function; } // =================================================================================== // HELPER FUNCTIONS // =================================================================================== /** * Update existing provenance * @param component reference location * @param setCommentProvenance function to update state variable * @param createNotification function to notify user of any issues with selection */ function updateCommentProvenance( component: string, setCommentProvenance: Function, createNotification: Function, ) { try { const [text, offsets] = extractMouseSelection(); if (text !== '') { setCommentProvenance({ component: component, text: text, offsets: offsets, }); } } catch (err) { // Notify user createNotification({ kind: 'error', title: 'Invalid selection', subtitle: 'cannot select text from different part of the page.', }); // Reset selection setCommentProvenance(undefined); } } // =================================================================================== // MAIN FUNCTION // =================================================================================== export default function Task({ taskId, onClose }: Props) { // Step 1: Initialize state and necessary variables const [addCommentModalOpen, setAddCommentModalOpen] = useState(false); const [taskCopierModalOpen, setTaskCopierModalOpen] = useState(false); const [commentProvenance, setCommentProvenance] = useState< TaskCommentProvenance | undefined >(undefined); // Step 2: Run effects // Step 2.a: Notification hook const { createNotification } = useNotification(); // Step 2.b: Handle task close event useEffect(() => { const handleEsc = (event) => { // If "Escape" key is pressed if (event.key === 'Escape') { // Step 1: Close task view onClose(); // Step 2: Stop event propogation event.preventDefault(); } }; window.addEventListener('keydown', handleEsc); return () => { window.removeEventListener('keydown', handleEsc); }; }, []); // Step 2.c: Fetch data from data store const { item: data, taskMap, updateTask } = useDataStore(); // Step 2.d: Configure model's map and metrics const [models, metrics] = useMemo(() => { if (data) { // Step 2.d.i: Make model_id -> model_name map const modelsMap = new Map( data.models.map((model) => [model.modelId, model]), ); return [modelsMap, data.metrics]; } // Default return return [undefined, undefined]; }, [data?.models, data?.metrics]); // Step 2.e: Fetch task const task = useMemo(() => { if (taskMap && taskId) { return taskMap.get(taskId); } }, [taskId, taskMap]); // Step 2.f: Initialize comment viewer status const [showComments, setShowComments] = useState( (task?.comments?.length && task.comments.length > 0) || false, ); // Step 2.g: Fetch evaluations for the current task const evaluations = useMemo(() => { let taskEvaluations: TaskEvaluation[] | undefined = undefined; if (data) { taskEvaluations = data.evaluations.filter( (evaluation) => evaluation.taskId === taskId, ); } return taskEvaluations; }, [taskId, task?.contexts, data?.documents, data?.evaluations]); // Step 3: Render return (
{ // Step 1: Create comment to add const commentToAdd = { comment: comment, author: author, created: Date.now(), updated: Date.now(), provenance: commentProvenance, }; // Step 2: Add comment to task updateTask(taskId, { comments: task?.comments ? [...task?.comments, commentToAdd] : [commentToAdd], }); // Step 3: Clear provenance setCommentProvenance(undefined); // Step 4: Close modal setAddCommentModalOpen(false); // Step 5: Open comments viewer setShowComments(true); }} onClose={() => { // Clear provenance setCommentProvenance(undefined); // Close modal setAddCommentModalOpen(false); }} provenance={commentProvenance} models={models} >
{ onClose(); }} > Press 'Escape' to close
{task && evaluations && (
{ // Step 1.a: Update global copy updateTask(task.taskId, { flagged: !task?.flagged, }); }} onClickCommentsIcon={() => { setShowComments(!showComments); }} onClickCopyToClipboardIcon={() => { setTaskCopierModalOpen(true); }} >
)} {task && models && evaluations && (
{task.comments && !isEmpty(task.comments) && showComments && (
{ updateTask(taskId, { comments: updatedComments, }); }} models={models} >
)} {task.taskType === 'rag' ? ( { updateCommentProvenance( provenance, setCommentProvenance, createNotification, ); }} /> ) : task.taskType === 'text_generation' ? ( { updateCommentProvenance( provenance, setCommentProvenance, createNotification, ); }} /> ) : task.taskType === 'chat' ? ( { updateCommentProvenance( provenance, setCommentProvenance, createNotification, ); }} /> ) : null}
{ setAddCommentModalOpen(true); }} onKeyDown={(event) => { if (event.key === 'Enter') { setAddCommentModalOpen(true); } }} >
)}
); }