/**
*
* 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 (
Tool ID: {tool.id}
{tool.function.name ? ({tool.function.name}) : null}
{tool.function.arguments ? (
{JSON.stringify(tool.function.arguments, null, 2)}
) : null}
);
}
function ToolResponse({
messageId,
message,
onSelection,
}: {
messageId: string;
message: ToolMessage;
onSelection?: Function;
}) {
// Step 1: Initialize state and necessary variables
const [documentIndex, setDocumentIndex] = useState(0);
// Step 2: Render
return (
Tool Call ID: {message.tool_call_id}
{message.name ? ({message.name}) : null}
{message.type === 'documents' && Array.isArray(message.content) ? (
) : message.type === 'json' ? (
{JSON.stringify(message.content, null, 2)}
) : (
{
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) => (
{line}
))
: message.content}
)}
);
}
function AssistantResponse({
messageId,
message,
onSelection,
}: {
messageId: string;
message: AssistantMessage;
onSelection?: Function;
}) {
return (
{message.content ? (
{
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) => (
{line}
))}
) : null}
{message.tool_calls
? message.tool_calls.map((tool, toolIdx) => {
return (
);
})
: null}
);
}
// ===================================================================================
// MAIN FUNCTIONS
// ===================================================================================
export default function ChatLine({
messageId,
message,
latestResponse,
onSelection,
focused,
}: ChatLineProps) {
// Step 1: Initialize state and necessary variables
const anchorRef = useRef(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 (
{message.role === 'system' ||
message.role === 'developer' ||
message.role === 'user' ? (
{
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) => (
{line}
))}
) : message.role === 'tool' ? (
//@ts-ignore
) : (
)}
);
}