kpfadnis's picture
chore (maintainance): Update depdencies, copyright and minimal support for chat template.
e23b66d
raw
history blame
7.94 kB
/**
*
* 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.
*
**/
import cx from 'classnames';
import Balancer from 'react-wrap-balancer';
import { useState, useEffect, useRef } from 'react';
import { CodeSnippet } from '@carbon/react';
import { Message, ToolCall, ToolMessage, AssistantMessage } from '@/src/types';
import Avatar from '@/src/components/avatar/Avatar';
import DocumentsViewer from '@/src/components/documents-viewer/DocumentsViewer';
import classes from './ChatLine.module.scss';
// ===================================================================================
// TYPES
// ===================================================================================
interface ChatLineProps {
messageId: string;
message: Message;
latestResponse?: boolean;
onSelection?: Function;
focused?: boolean;
}
// ===================================================================================
// RENDER FUNCTIONS
// ===================================================================================
function Tool({ tool }: { tool: ToolCall }) {
return (
<div className={cx(classes.message, classes.toolCall)}>
<span>
Tool ID: {tool.id}&nbsp;
{tool.function.name ? <span>({tool.function.name})</span> : null}
</span>
{tool.function.arguments ? (
<CodeSnippet type="multi" hideCopyButton wrapText>
{JSON.stringify(tool.function.arguments, null, 2)}
</CodeSnippet>
) : null}
</div>
);
}
function ToolResponse({
messageId,
message,
onSelection,
}: {
messageId: string;
message: ToolMessage;
onSelection?: Function;
}) {
// Step 1: Initialize state and necessary variables
const [documentIndex, setDocumentIndex] = useState<number>(0);
// Step 2: Render
return (
<div className={cx(classes.message, classes.toolResponse)}>
<span>
Tool ID: {message.tool_id}&nbsp;
{message.name ? <span>({message.name})</span> : null}
</span>
{message.type === 'documents' && Array.isArray(message.content) ? (
<DocumentsViewer
key={`${messageId}__documents--${message.content.length}`}
id={`${messageId}__documents`}
documents={message.content}
documentIndex={documentIndex}
setDocumentIndex={setDocumentIndex}
onSelection={onSelection}
></DocumentsViewer>
) : message.type === 'json' ? (
<CodeSnippet type="multi" hideCopyButton wrapText>
{JSON.stringify(message.content, null, 2)}
</CodeSnippet>
) : (
<Balancer
className={cx(classes.message, classes.toolMessage)}
ratio={0.2}
onMouseDown={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].content`,
);
}
}}
onMouseUp={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].content`,
);
}
}}
>
{typeof message.content === 'string'
? message.content.split('\n').map((line, i) => (
<span key={i}>
{line}
<br />
</span>
))
: message.content}
</Balancer>
)}
</div>
);
}
function AssistantResponse({
messageId,
message,
onSelection,
}: {
messageId: string;
message: AssistantMessage;
onSelection?: Function;
}) {
return (
<div className={classes.assistantResponse}>
{message.content ? (
<Balancer
className={cx(classes.message, classes.assistantMessage)}
ratio={0.2}
onMouseDown={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].content`,
);
}
}}
onMouseUp={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].content`,
);
}
}}
>
{message.content.split('\n').map((line, i) => (
<span key={i}>
{line}
<br />
</span>
))}
</Balancer>
) : null}
{message.tool_calls
? message.tool_calls.map((tool, toolIdx) => {
return (
<Tool key={`message-${messageId}__tool-${toolIdx}`} tool={tool} />
);
})
: null}
</div>
);
}
// ===================================================================================
// MAIN FUNCTIONS
// ===================================================================================
export default function ChatLine({
messageId,
message,
latestResponse,
onSelection,
focused,
}: ChatLineProps) {
// Step 1: Initialize state and necessary variables
const anchorRef = useRef<HTMLDivElement>(null);
// Step 2: Run effects
// Step 2.a: Scroll into view
useEffect(() => {
if (anchorRef.current && focused) {
anchorRef.current.scrollIntoView({
behavior: 'smooth',
block: message.role === 'user' ? 'start' : 'center',
inline: 'center',
});
}
}, [focused, message.role]);
// Step 3: Render
// Step 3.a: Return "null" if message is undefined
if (!message) {
return null;
}
// Step 3.b: Render chat line
return (
<div
ref={anchorRef}
className={cx(classes.line, {
[classes.assistantLine]: message.role === 'assistant',
[classes.latestResponse]: latestResponse,
})}
>
<Avatar role={message.role} />
<div
className={cx(
classes.baloon,
message.role === 'assistant'
? //@ts-ignore
message.tool_calls
? classes.toolCallBaloon
: classes.assistantBaloon
: message.role === 'tool'
? classes.toolResponseBaloon
: null,
)}
>
{message.role === 'system' ||
message.role === 'developer' ||
message.role === 'user' ? (
<Balancer
className={cx(classes.message)}
ratio={0.2}
onMouseDown={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].text`,
);
}
}}
onMouseUp={() => {
if (onSelection) {
onSelection(
`messages[${messageId.split('--').slice(-1)[0]}].text`,
);
}
}}
>
{message.content.split('\n').map((line, i) => (
<span key={i}>
{line}
<br />
</span>
))}
</Balancer>
) : message.role === 'tool' ? (
//@ts-ignore
<ToolResponse messageId={messageId} message={message} />
) : (
<AssistantResponse
messageId={messageId}
//@ts-ignore
message={message}
onSelection={onSelection}
/>
)}
</div>
</div>
);
}