Spaces:
Running
Running
/** | |
* | |
* 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<boolean>(false); | |
const [taskCopierModalOpen, setTaskCopierModalOpen] = | |
useState<boolean>(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<string, Model>( | |
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<boolean>( | |
(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 ( | |
<div className={classes.page}> | |
<AddCommentModal | |
open={addCommentModalOpen} | |
selectedText={commentProvenance ? commentProvenance.text : undefined} | |
onSubmit={(comment: string, author: string) => { | |
// 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} | |
></AddCommentModal> | |
<div className={classes.pageHint}> | |
<Tag | |
type={'outline'} | |
onClick={() => { | |
onClose(); | |
}} | |
> | |
Press 'Escape' to close | |
</Tag> | |
</div> | |
{task && evaluations && ( | |
<div> | |
<TaskTile | |
task={task} | |
evaluations={evaluations} | |
expanded={false} | |
onClickFlagIcon={() => { | |
// Step 1.a: Update global copy | |
updateTask(task.taskId, { | |
flagged: !task?.flagged, | |
}); | |
}} | |
onClickCommentsIcon={() => { | |
setShowComments(!showComments); | |
}} | |
onClickCopyToClipboardIcon={() => { | |
setTaskCopierModalOpen(true); | |
}} | |
></TaskTile> | |
</div> | |
)} | |
{task && models && evaluations && ( | |
<div className={classes.taskContainer}> | |
{task.comments && !isEmpty(task.comments) && showComments && ( | |
<div className={classes.commentsContainer}> | |
<ViewComments | |
comments={task.comments} | |
onUpdate={(updatedComments) => { | |
updateTask(taskId, { | |
comments: updatedComments, | |
}); | |
}} | |
models={models} | |
></ViewComments> | |
</div> | |
)} | |
{task.taskType === 'rag' ? ( | |
<RAGTask | |
task={task} | |
models={models} | |
metrics={metrics} | |
taskCopierModalOpen={taskCopierModalOpen} | |
setTaskCopierModalOpen={setTaskCopierModalOpen} | |
updateCommentProvenance={(provenance: string) => { | |
updateCommentProvenance( | |
provenance, | |
setCommentProvenance, | |
createNotification, | |
); | |
}} | |
/> | |
) : task.taskType === 'text_generation' ? ( | |
<TextGenerationTask | |
task={task} | |
models={models} | |
metrics={metrics} | |
taskCopierModalOpen={taskCopierModalOpen} | |
setTaskCopierModalOpen={setTaskCopierModalOpen} | |
updateCommentProvenance={(provenance: string) => { | |
updateCommentProvenance( | |
provenance, | |
setCommentProvenance, | |
createNotification, | |
); | |
}} | |
/> | |
) : task.taskType === 'chat' ? ( | |
<ChatTask | |
task={task} | |
models={models} | |
metrics={metrics} | |
taskCopierModalOpen={taskCopierModalOpen} | |
setTaskCopierModalOpen={setTaskCopierModalOpen} | |
updateCommentProvenance={(provenance: string) => { | |
updateCommentProvenance( | |
provenance, | |
setCommentProvenance, | |
createNotification, | |
); | |
}} | |
/> | |
) : null} | |
<div | |
key={'add-comment-btn'} | |
tabIndex={0} | |
className={classes.addCommentBtn} | |
onClick={() => { | |
setAddCommentModalOpen(true); | |
}} | |
onKeyDown={(event) => { | |
if (event.key === 'Enter') { | |
setAddCommentModalOpen(true); | |
} | |
}} | |
> | |
<Tooltip align={'top-right'} label={'Click to add comment'}> | |
<AddComment size={20}></AddComment> | |
</Tooltip> | |
</div> | |
</div> | |
)} | |
</div> | |
); | |
} | |