Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions locales/en/plugin__lightspeed-console-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"Configure events attachment": "Configure events attachment",
"Configure log attachment": "Configure log attachment",
"Confirm chat deletion": "Confirm chat deletion",
"Content": "Content",
"Conversation history has been truncated to fit within context window.": "Conversation history has been truncated to fit within context window.",
"Copied": "Copied",
"Copy conversation": "Copy conversation",
Expand Down Expand Up @@ -59,6 +60,7 @@
"Large prompt": "Large prompt",
"Leave": "Leave",
"Logs": "Logs",
"MCP server": "MCP server",
"Minimize": "Minimize",
"Most recent {{lines}} lines": "Most recent {{lines}} lines",
"Most recent {{numEvents}} events": "Most recent {{numEvents}} events",
Expand All @@ -80,13 +82,16 @@
"Revert to original": "Revert to original",
"Save": "Save",
"Send a message...": "Send a message...",
"Status": "Status",
"Stay": "Stay",
"Structured content": "Structured content",
"Submit": "Submit",
"The following output was generated when running <2>{{name}}</2> with arguments <5>{{argsFormatted}}</5>.": "The following output was generated when running <2>{{name}}</2> with arguments <5>{{argsFormatted}}</5>.",
"The following output was generated when running <2>{{name}}</2> with no arguments.": "The following output was generated when running <2>{{name}}</2> with no arguments.",
"The OpenShift Lightspeed service is not yet ready to receive requests. If this message persists, please check the OLSConfig.": "The OpenShift Lightspeed service is not yet ready to receive requests. If this message persists, please check the OLSConfig.",
"Tool output": "Tool output",
"Total size of attachments exceeds {{max}} characters.": "Total size of attachments exceeds {{max}} characters.",
"UI resource": "UI resource",
"Upload from computer": "Upload from computer",
"Uploaded file is not valid YAML": "Uploaded file is not valid YAML",
"Uploaded file is too large. Max size is {{max}} MB.": "Uploaded file is too large. Max size is {{max}} MB.",
Expand Down
19 changes: 17 additions & 2 deletions src/components/Prompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,23 @@ const Prompt: React.FC<PromptProps> = ({ scrollIntoView }) => {
const { args, id, name: toolName } = json.data;
dispatch(chatHistoryUpdateTool(chatEntryID, id, { name: toolName, args }));
} else if (json.event === 'tool_result') {
const { content, id, status } = json.data;
dispatch(chatHistoryUpdateTool(chatEntryID, id, { content, status }));
const {
content,
id,
status,
ui_resource_uri: uiResourceUri,
server_name: serverName,
structured_content: structuredContent,
} = json.data;
dispatch(
chatHistoryUpdateTool(chatEntryID, id, {
content,
status,
...(uiResourceUri && { uiResourceUri }),
...(serverName && { serverName }),
...(structuredContent && { structuredContent }),
}),
);
} else if (json.event === 'error') {
dispatch(
chatHistoryUpdateByID(chatEntryID, {
Expand Down
112 changes: 96 additions & 16 deletions src/components/ResponseToolModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,19 @@ import { Map as ImmutableMap } from 'immutable';
import * as React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Alert, CodeBlock, CodeBlockAction, CodeBlockCode, Icon } from '@patternfly/react-core';
import {
Alert,
CodeBlock,
CodeBlockAction,
CodeBlockCode,
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
Icon,
Label,
Title,
} from '@patternfly/react-core';
import { InfoCircleIcon } from '@patternfly/react-icons';

import { openToolClear } from '../redux-actions';
Expand Down Expand Up @@ -32,12 +44,26 @@ const ToolModal: React.FC = () => {
if (!tool) {
return null;
}
const { args, content, name, status } = tool.toJS();

const argsFormatted = Object.entries(args)
const { args, content, name, serverName, status, structuredContent, uiResourceUri } =
tool.toJS() as {
args: Record<string, unknown>;
content: string;
name: string;
serverName?: string;
status: string;
structuredContent?: Record<string, unknown>;
uiResourceUri?: string;
};

const argsFormatted = Object.entries(args ?? {})
.map(([key, value]) => `${key}=${value}`)
.join(', ');

const structuredContentFormatted = structuredContent
? JSON.stringify(structuredContent, null, 2)
: undefined;

return (
<Modal
className="ols-plugin__attachment-modal"
Expand Down Expand Up @@ -76,20 +102,51 @@ const ToolModal: React.FC = () => {
</Trans>
)}
</p>

<DescriptionList className="ols-plugin__tool-metadata" isCompact isHorizontal>
<DescriptionListGroup>
<DescriptionListTerm>{t('Status')}</DescriptionListTerm>
<DescriptionListDescription>
<Label color={status === 'error' ? 'red' : status === 'success' ? 'green' : 'yellow'}>
{status}
</Label>
</DescriptionListDescription>
</DescriptionListGroup>
{serverName && (
<DescriptionListGroup>
<DescriptionListTerm>{t('MCP server')}</DescriptionListTerm>
<DescriptionListDescription>{serverName}</DescriptionListDescription>
</DescriptionListGroup>
)}
{uiResourceUri && (
<DescriptionListGroup>
<DescriptionListTerm>{t('UI resource')}</DescriptionListTerm>
<DescriptionListDescription>
<span className="ols-plugin__code-inline">{uiResourceUri}</span>
</DescriptionListDescription>
</DescriptionListGroup>
)}
</DescriptionList>

{content ? (
<CodeBlock
actions={
<>
<CodeBlockAction />
<CodeBlockAction>
<CopyAction value={content} />
</CodeBlockAction>
</>
}
className="ols-plugin__code-block ols-plugin__code-block--attachment"
>
<CodeBlockCode className="ols-plugin__code-block-code">{content}</CodeBlockCode>
</CodeBlock>
<>
<Title className="ols-plugin__tool-section-title" headingLevel="h4">
{t('Content')}
</Title>
<CodeBlock
actions={
<>
<CodeBlockAction />
<CodeBlockAction>
<CopyAction value={content} />
</CodeBlockAction>
</>
}
className="ols-plugin__code-block ols-plugin__code-block--attachment"
>
<CodeBlockCode className="ols-plugin__code-block-code">{content}</CodeBlockCode>
</CodeBlock>
</>
) : (
<Alert
className="ols-plugin__alert"
Expand All @@ -98,6 +155,29 @@ const ToolModal: React.FC = () => {
variant="info"
/>
)}

{structuredContentFormatted && (
<>
<Title className="ols-plugin__tool-section-title" headingLevel="h4">
{t('Structured content')}
</Title>
<CodeBlock
actions={
<>
<CodeBlockAction />
<CodeBlockAction>
<CopyAction value={structuredContentFormatted} />
</CodeBlockAction>
</>
}
className="ols-plugin__code-block ols-plugin__code-block--attachment"
>
<CodeBlockCode className="ols-plugin__code-block-code">
{structuredContentFormatted}
</CodeBlockCode>
</CodeBlock>
</>
)}
</Modal>
);
};
Expand Down
27 changes: 18 additions & 9 deletions src/components/ResponseTools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Map as ImmutableMap } from 'immutable';
import * as React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Label, LabelGroup } from '@patternfly/react-core';
import { CodeIcon, InfoCircleIcon } from '@patternfly/react-icons';
import { CodeIcon, ExternalLinkAltIcon, InfoCircleIcon } from '@patternfly/react-icons';

import { openToolSet } from '../redux-actions';
import { State } from '../redux-reducers';
Expand All @@ -23,16 +23,25 @@ const ToolLabel: React.FC<ToolProps> = ({ entryIndex, toolID }) => {
dispatch(openToolSet(entryIndex, toolID));
}, [dispatch, entryIndex, toolID]);

const isError = tool.get('status') === 'error';
const status = tool.get('status') as string | undefined;
const isError = status === 'error';
const isTruncated = status === 'truncated';
const hasUI = !!tool.get('uiResourceUri');

const color = isError ? 'red' : isTruncated ? 'yellow' : hasUI ? 'blue' : undefined;
const icon = isError ? (
<InfoCircleIcon />
) : isTruncated ? (
<InfoCircleIcon />
) : hasUI ? (
<ExternalLinkAltIcon />
) : (
<CodeIcon />
);

return (
<Label
color={isError ? 'red' : undefined}
icon={isError ? <InfoCircleIcon /> : <CodeIcon />}
onClick={onClick}
textMaxWidth="16ch"
>
{tool.get('name')}
<Label color={color} icon={icon} onClick={onClick} textMaxWidth="16ch">
{tool.get('name') as string}
</Label>
);
};
Expand Down
10 changes: 10 additions & 0 deletions src/components/general-page.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,13 @@
max-width: 30rem;
text-align: center;
}

.ols-plugin__tool-metadata {
margin-bottom: var(--pf-t--global--spacer--md);
margin-top: var(--pf-t--global--spacer--md);
}

.ols-plugin__tool-section-title {
margin-bottom: var(--pf-t--global--spacer--sm);
margin-top: var(--pf-t--global--spacer--md);
}
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export type Tool = {
args: { [key: string]: Array<string> };
content: string;
name: string;
status: 'error' | 'success';
status: 'error' | 'success' | 'truncated';
// MCP app fields (optional - present when tool provides UI)
uiResourceUri?: string;
serverName?: string;
structuredContent?: Record<string, unknown>;
};

type ChatEntryUser = {
Expand Down