diff --git a/.changeset/honest-numbers-wear.md b/.changeset/honest-numbers-wear.md
new file mode 100644
index 000000000..4c8dd37bd
--- /dev/null
+++ b/.changeset/honest-numbers-wear.md
@@ -0,0 +1,5 @@
+---
+'@orchestrator-ui/orchestrator-ui-components': minor
+---
+
+Added tool calling chain component to agent and made it possible to export to CSV
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/ExportButton.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/ExportButton.tsx
new file mode 100644
index 000000000..d0dade2f0
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/ExportButton.tsx
@@ -0,0 +1,96 @@
+import React from 'react';
+
+import { useTranslations } from 'next-intl';
+
+import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui';
+
+import { useShowToastMessage, useWithOrchestratorTheme } from '@/hooks';
+import { useLazyGetAgentExportQuery } from '@/rtk/endpoints/agentExport';
+import { GraphQLPageInfo } from '@/types';
+import { getCsvFileNameWithDate } from '@/utils';
+import { csvDownloadHandler } from '@/utils/csvDownload';
+
+import { getExportButtonStyles } from './styles';
+
+export type ExportData = {
+ action: string;
+ download_url: string;
+ message: string;
+};
+
+export type ExportButtonProps = {
+ exportData: ExportData;
+};
+
+type ExportApiResponse = {
+ page: object[];
+ pageInfo?: GraphQLPageInfo;
+};
+
+export function ExportButton({ exportData }: ExportButtonProps) {
+ const { showToastMessage } = useShowToastMessage();
+ const tError = useTranslations('errors');
+ const [triggerExport, { isFetching }] = useLazyGetAgentExportQuery();
+
+ const {
+ containerStyle,
+ buttonWrapperStyle,
+ titleStyle,
+ fileRowStyle,
+ fileInfoStyle,
+ filenameStyle,
+ downloadButtonStyle,
+ } = useWithOrchestratorTheme(getExportButtonStyles);
+
+ const filename = getCsvFileNameWithDate('export');
+
+ const onDownloadClick = async () => {
+ const data = await triggerExport(exportData.download_url).unwrap();
+
+ const keyOrder = data.page.length > 0 ? Object.keys(data.page[0]) : [];
+
+ const handleExport = csvDownloadHandler(
+ async () => data,
+ (data: ExportApiResponse) => data.page,
+ (data: ExportApiResponse) =>
+ data.pageInfo ?? {
+ totalItems: data.page.length,
+ startCursor: 0,
+ endCursor: data.page.length - 1,
+ hasNextPage: false,
+ hasPreviousPage: false,
+ sortFields: [],
+ filterFields: [],
+ },
+ keyOrder,
+ filename,
+ showToastMessage,
+ tError,
+ );
+
+ await handleExport();
+ };
+
+ return (
+
+
+ {exportData.message && (
+
{exportData.message}
+ )}
+
+
+
+ {filename}
+
+
+ {isFetching ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/index.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/index.ts
new file mode 100644
index 000000000..6b75481ab
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/index.ts
@@ -0,0 +1 @@
+export * from './ExportButton';
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/styles.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/styles.ts
new file mode 100644
index 000000000..30ddd0598
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ExportButton/styles.ts
@@ -0,0 +1,69 @@
+import { css } from '@emotion/react';
+
+import { WfoTheme } from '@/hooks';
+
+export const getExportButtonStyles = ({ theme }: WfoTheme) => {
+ const containerStyle = css({
+ marginTop: theme.size.xl,
+ marginBottom: theme.size.xl,
+ width: '100%',
+ });
+
+ const buttonWrapperStyle = css({
+ backgroundColor: theme.colors.lightestShade,
+ padding: `${theme.size.xl} ${theme.size.xl}`,
+ border: `${theme.border.width.thin} solid transparent`,
+ display: 'flex',
+ flexDirection: 'column',
+ gap: theme.size.l,
+ });
+
+ const titleStyle = css({
+ fontSize: theme.size.m,
+ fontWeight: theme.font.weight.semiBold,
+ color: theme.colors.darkestShade,
+ });
+
+ const fileRowStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ gap: theme.size.m,
+ border: `${theme.border.width.thin} solid ${theme.colors.lightShade}`,
+ borderRadius: theme.border.radius.medium,
+ padding: `${theme.size.m} ${theme.size.l}`,
+ backgroundColor: theme.colors.emptyShade,
+ cursor: 'pointer',
+ });
+
+ const fileInfoStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ gap: theme.size.m,
+ flex: 1,
+ color: theme.colors.darkestShade,
+ });
+
+ const filenameStyle = css({
+ fontSize: theme.size.m,
+ fontWeight: theme.font.weight.medium,
+ color: theme.colors.darkestShade,
+ });
+
+ const downloadButtonStyle = css({
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ color: theme.colors.darkestShade,
+ });
+
+ return {
+ containerStyle,
+ buttonWrapperStyle,
+ titleStyle,
+ fileRowStyle,
+ fileInfoStyle,
+ filenameStyle,
+ downloadButtonStyle,
+ };
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/index.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/index.ts
deleted file mode 100644
index bacbb5ef8..000000000
--- a/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './FilterDisplay';
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/DiscoverFilterPathsDisplay.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/DiscoverFilterPathsDisplay.tsx
new file mode 100644
index 000000000..c950a3d03
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/DiscoverFilterPathsDisplay.tsx
@@ -0,0 +1,114 @@
+import React from 'react';
+
+import { EuiSpacer, EuiText } from '@elastic/eui';
+
+import { WfoBadge } from '@/components/WfoBadges';
+import { WfoPathBreadcrumb } from '@/components/WfoSearchPage/WfoSearchResults/WfoPathBreadcrumb';
+
+interface DiscoverFilterPathsResult {
+ status?: string;
+ leaves?: Array<{
+ paths?: string[];
+ name?: string;
+ }>;
+}
+
+type DiscoverFilterPathsDisplayProps = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ result?: any;
+ parameters: {
+ field_names?: string[];
+ entity_type?: string;
+ };
+};
+
+export const DiscoverFilterPathsDisplay = ({
+ parameters,
+ result,
+}: DiscoverFilterPathsDisplayProps) => {
+ const { field_names = [] } = parameters;
+
+ const foundFields: [string, DiscoverFilterPathsResult][] = result
+ ? Object.entries(
+ result as Record,
+ ).filter(([, fieldResult]) => fieldResult.status !== 'NOT_FOUND')
+ : [];
+
+ // Count total paths across all found fields
+ const totalPaths = foundFields.reduce((count, [, fieldResult]) => {
+ const pathCount =
+ fieldResult.leaves?.reduce((leafCount: number, leaf) => {
+ return leafCount + (leaf.paths?.length || 1);
+ }, 0) || 0;
+ return count + pathCount;
+ }, 0);
+
+ return (
+
+ {field_names.length > 0 && (
+ <>
+
+ Looking for{' '}
+ {field_names.map((name, idx) => (
+
+ {idx > 0 && ', '}
+
+ {name}
+
+
+ ))}
+
+
+ >
+ )}
+
+ {result && totalPaths > 0 && (
+
+
+
+ Found {totalPaths} path
+ {totalPaths > 1 ? 's' : ''}:
+
+
+
+ {foundFields.map(([fieldName, fieldResult]) => (
+
+ {fieldResult.leaves &&
+ fieldResult.leaves.length > 0 &&
+ fieldResult.leaves.map(
+ (leaf, leafIdx: number) => {
+ const paths =
+ leaf.paths ||
+ (leaf.name ? [leaf.name] : []);
+ return (
+
+ {paths.map(
+ (
+ path: string,
+ pathIdx: number,
+ ) => (
+
+
+
+ ),
+ )}
+
+ );
+ },
+ )}
+
+ ))}
+
+ )}
+
+ );
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/RunSearchDisplay.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/RunSearchDisplay.tsx
new file mode 100644
index 000000000..864ab43b3
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/RunSearchDisplay.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+
+import { WfoBadge } from '@/components/WfoBadges';
+
+type RunSearchDisplayProps = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ result?: any;
+ parameters: {
+ limit?: number;
+ };
+};
+
+export const RunSearchDisplay = ({ parameters }: RunSearchDisplayProps) => {
+ const { limit = 10 } = parameters;
+
+ return (
+
+
+
+
+ Results Limit
+
+
+
+
+ {limit}
+
+
+
+
+ );
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/styles.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.styles.ts
similarity index 100%
rename from packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/styles.ts
rename to packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.styles.ts
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/FilterDisplay.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.tsx
similarity index 62%
rename from packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/FilterDisplay.tsx
rename to packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.tsx
index aa1c84e6e..5dce48370 100644
--- a/packages/orchestrator-ui-components/src/components/WfoAgent/FilterDisplay/FilterDisplay.tsx
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/SetFilterTreeDisplay.tsx
@@ -2,13 +2,7 @@ import React from 'react';
import { useTranslations } from 'next-intl';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiPanel,
- EuiSpacer,
- EuiText,
-} from '@elastic/eui';
+import { EuiText } from '@elastic/eui';
import { WfoBadge } from '@/components/WfoBadges';
import { WfoPathBreadcrumb } from '@/components/WfoSearchPage/WfoSearchResults/WfoPathBreadcrumb';
@@ -16,22 +10,13 @@ import {
getOperatorDisplay,
isCondition,
} from '@/components/WfoSearchPage/utils';
-import { useWithOrchestratorTheme } from '@/index';
-import { AnySearchParameters, Condition, Group, PathDataType } from '@/types';
+import { useWithOrchestratorTheme } from '@/hooks';
+import { Condition, Group, PathDataType } from '@/types';
-import { getFilterDisplayStyles } from './styles';
+import { getFilterDisplayStyles } from './SetFilterTreeDisplay.styles';
const DEPTH_INDENT = 16;
-type FilterDisplayProps = {
- parameters: {
- action?: AnySearchParameters['action'] | string;
- entity_type?: AnySearchParameters['entity_type'] | string;
- filters?: Group | null;
- query?: string | null;
- };
-};
-
interface BetweenValue {
start?: string | number;
end?: string | number;
@@ -39,9 +24,17 @@ interface BetweenValue {
to?: string | number;
}
-export function FilterDisplay({ parameters }: FilterDisplayProps) {
- const t = useTranslations('agent.page');
+type SetFilterTreeDisplayProps = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ result?: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ parameters: any;
+};
+export const SetFilterTreeDisplay = ({
+ parameters,
+}: SetFilterTreeDisplayProps) => {
+ const t = useTranslations('agent.page');
const {
wrapStyle,
columnGroupWrapStyle,
@@ -50,15 +43,9 @@ export function FilterDisplay({ parameters }: FilterDisplayProps) {
operatorStyle,
valueStyle,
} = useWithOrchestratorTheme(getFilterDisplayStyles);
- const { action, entity_type, filters, query } = parameters ?? {};
- if (!parameters || Object.keys(parameters).length === 0) return null;
-
- const sectionTitle = (text: string) => (
-
- {text}
-
- );
+ // Parameters might be the Group directly, or wrapped in a filters property
+ const filters = (parameters?.filters || parameters) as Group;
const formatFilterValue = (condition: Condition['condition']): string => {
if ('value' in condition && condition.value !== undefined) {
@@ -136,47 +123,13 @@ export function FilterDisplay({ parameters }: FilterDisplayProps) {
);
};
- return (
-
-
-
- {sectionTitle(t('action'))}
-
-
- {action || 'N/A'}
-
-
-
-
- {sectionTitle(t('entityType'))}
-
-
- {entity_type || 'N/A'}
-
-
-
- {query ? (
-
- {sectionTitle(t('searchQuery'))}
-
-
- "{query}"
-
-
- ) : null}
-
-
-
- {sectionTitle(t('activeFilters'))}
-
+ if (!filters || !filters.children || filters.children.length === 0) {
+ return (
+
+ {t('noFiltersApplied')}
+
+ );
+ }
- {filters && filters.children && filters.children.length > 0 ? (
- renderFilterGroup(filters)
- ) : (
-
- {t('noFiltersApplied')}
-
- )}
-
- );
-}
+ return {renderFilterGroup(filters)}
;
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/StartNewSearchDisplay.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/StartNewSearchDisplay.tsx
new file mode 100644
index 000000000..4de06be90
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/StartNewSearchDisplay.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+
+import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
+
+import { WfoBadge } from '@/components/WfoBadges';
+
+type StartNewSearchDisplayProps = {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ result?: any;
+ parameters: {
+ entity_type?: string;
+ action?: string;
+ query?: string;
+ };
+};
+
+export const StartNewSearchDisplay = ({
+ parameters,
+}: StartNewSearchDisplayProps) => {
+ const { entity_type, action, query } = parameters;
+
+ return (
+
+
+ {entity_type && (
+
+
+ Entity Type
+
+
+
+ {entity_type}
+
+
+ )}
+ {action && (
+
+
+ Action
+
+
+
+ {action}
+
+
+ )}
+
+ {query && (
+ <>
+
+
+ Query
+
+
+
+ "{query}"
+
+ >
+ )}
+
+ );
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/ToolProgress.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/ToolProgress.tsx
new file mode 100644
index 000000000..79dbf6467
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/ToolProgress.tsx
@@ -0,0 +1,138 @@
+import React, { useState } from 'react';
+
+import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui';
+
+import { useWithOrchestratorTheme } from '@/hooks';
+import { useOrchestratorTheme } from '@/hooks/useOrchestratorTheme';
+import {
+ WfoCheckmarkCircleFill,
+ WfoChevronDown,
+ WfoChevronUp,
+ WfoXCircleFill,
+} from '@/icons';
+import { WfoWrench } from '@/icons/heroicons';
+
+import { DiscoverFilterPathsDisplay } from './DiscoverFilterPathsDisplay';
+import { RunSearchDisplay } from './RunSearchDisplay';
+import { SetFilterTreeDisplay } from './SetFilterTreeDisplay';
+import { StartNewSearchDisplay } from './StartNewSearchDisplay';
+import { getToolProgressStyles } from './styles';
+
+type ToolProgressProps = {
+ name: string;
+ status: 'executing' | 'inProgress' | 'complete' | 'failed';
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ args?: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ result?: any;
+};
+
+// Mapping of tool names to their display components
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const TOOL_DISPLAY_COMPONENTS: Record> = {
+ set_filter_tree: SetFilterTreeDisplay,
+ start_new_search: StartNewSearchDisplay,
+ run_search: RunSearchDisplay,
+ discover_filter_paths: DiscoverFilterPathsDisplay,
+};
+
+export const ToolProgress = ({
+ name,
+ status,
+ args,
+ result,
+}: ToolProgressProps) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+
+ const {
+ containerStyle,
+ containerClickableStyle,
+ headerStyle,
+ nameStyle,
+ expandedContentStyle,
+ iconSize,
+ iconStyle,
+ } = useWithOrchestratorTheme(getToolProgressStyles);
+
+ const { theme } = useOrchestratorTheme();
+
+ const renderStatus = () => {
+ if (status === 'complete') {
+ return (
+
+ );
+ }
+ if (status === 'inProgress' || status === 'executing') {
+ return ;
+ }
+ if (status === 'failed') {
+ return (
+
+ );
+ }
+ return null;
+ };
+
+ const DisplayComponent = TOOL_DISPLAY_COMPONENTS[name];
+ const hasArgs = DisplayComponent && args;
+
+ return (
+
+
hasArgs && setIsExpanded(!isExpanded)}
+ >
+
+
+
+
+
+
+
+ {name}
+
+
+ {renderStatus()}
+
+
+ {hasArgs &&
+ (isExpanded ? (
+
+ ) : (
+
+ ))}
+
+
+
+ {hasArgs && isExpanded && (
+
+
+
+ )}
+
+ );
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/index.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/index.ts
new file mode 100644
index 000000000..71c6523ab
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/index.ts
@@ -0,0 +1 @@
+export { ToolProgress } from './ToolProgress';
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/styles.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/styles.ts
new file mode 100644
index 000000000..d4d06deff
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/ToolProgress/styles.ts
@@ -0,0 +1,50 @@
+import { css } from '@emotion/react';
+
+import { WfoTheme } from '@/hooks';
+
+export const getToolProgressStyles = ({ theme }: WfoTheme) => {
+ const containerStyle = css({
+ border: `${theme.border.width.thin} solid ${theme.colors.lightShade}`,
+ borderRadius: theme.border.radius.medium,
+ backgroundColor: theme.colors.emptyShade,
+ transition: `all ${theme.animation.normal} ease`,
+ });
+
+ const containerClickableStyle = css({
+ cursor: 'pointer',
+ '&:hover': {
+ borderColor: theme.colors.primary,
+ backgroundColor: theme.colors.lightestShade,
+ },
+ });
+
+ const headerStyle = css({
+ padding: `${theme.size.base} ${theme.size.l}`,
+ });
+
+ const nameStyle = css({
+ fontSize: theme.size.m,
+ fontWeight: theme.font.weight.medium,
+ });
+
+ const expandedContentStyle = css({
+ borderTop: `${theme.border.width.thin} solid ${theme.colors.lightShade}`,
+ padding: `${theme.size.base} ${theme.size.l}`,
+ });
+
+ const iconSize = 18;
+
+ const iconStyle = css({
+ color: theme.colors.subduedText,
+ });
+
+ return {
+ containerStyle,
+ containerClickableStyle,
+ headerStyle,
+ nameStyle,
+ expandedContentStyle,
+ iconSize,
+ iconStyle,
+ };
+};
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/WfoAgent/WfoAgent.tsx b/packages/orchestrator-ui-components/src/components/WfoAgent/WfoAgent/WfoAgent.tsx
index 831a8e5db..b7966f618 100644
--- a/packages/orchestrator-ui-components/src/components/WfoAgent/WfoAgent/WfoAgent.tsx
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/WfoAgent/WfoAgent.tsx
@@ -2,28 +2,44 @@ import React from 'react';
import { useTranslations } from 'next-intl';
-import { useCoAgent } from '@copilotkit/react-core';
+import {
+ CatchAllActionRenderProps,
+ useCoAgent,
+ useCoAgentStateRender,
+ useCopilotAction,
+} from '@copilotkit/react-core';
import { CopilotSidebar } from '@copilotkit/react-ui';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui';
import { WfoSearchResults } from '@/components/WfoSearchPage/WfoSearchResults';
-import { AnySearchParameters, AnySearchResult, PathFilter } from '@/types';
-
-import { FilterDisplay } from '../FilterDisplay';
+import { AnySearchParameters, SearchResult } from '@/types';
+
+import { ExportButton, ExportData } from '../ExportButton';
+import { ToolProgress } from '../ToolProgress';
+
+type SearchResultsData = {
+ action: string;
+ query_id: string;
+ results_url: string;
+ total_count: number;
+ message: string;
+ results: SearchResult[];
+};
type SearchState = {
- parameters: AnySearchParameters;
- results: AnySearchResult[];
+ run_id: string | null;
+ query_id: string | null;
+ parameters: AnySearchParameters | null;
+ results_data: SearchResultsData | null;
+ export_data: ExportData | null;
};
const initialState: SearchState = {
- parameters: {
- action: 'select',
- entity_type: 'SUBSCRIPTION',
- filters: [] as PathFilter[],
- query: null,
- },
- results: [],
+ run_id: null,
+ query_id: null,
+ parameters: null,
+ results_data: null,
+ export_data: null,
};
export function WfoAgent() {
@@ -34,23 +50,38 @@ export function WfoAgent() {
name: 'query_agent',
initialState,
});
- const { parameters, results } = state;
-
- const hasStarted = !!(
- state.parameters &&
- Array.isArray(state.parameters.filters) &&
- state.parameters.filters.length > 0
- );
-
- const isLoadingResults =
- hasStarted && (!state.results || state.results.length === 0);
+ const { results_data } = state;
+
+ // Automatically render all tool calls
+ useCopilotAction({
+ name: '*',
+ render: ({
+ name,
+ status,
+ args,
+ result,
+ }: CatchAllActionRenderProps<[]>) => {
+ return (
+
+ );
+ },
+ });
- const displayParameters = parameters && {
- ...parameters,
- filters: Array.isArray(parameters.filters)
- ? { op: 'AND' as const, children: parameters.filters }
- : parameters.filters,
- };
+ // Render export button from state
+ useCoAgentStateRender({
+ name: 'query_agent',
+ render: ({ state }) => {
+ if (!state?.export_data || state.export_data.action !== 'export') {
+ return null;
+ }
+ return ;
+ },
+ });
return (
@@ -60,29 +91,25 @@ export function WfoAgent() {
-
- {tPage('filledParameters')}
-
-
- {displayParameters && (
-
- )}
-
-
-
-
- {tPage('results')}{' '}
- {results ? `(${results.length})` : ''}
-
-
-
- {}}
- />
+ {results_data && results_data.action === 'view_results' && (
+ <>
+ {results_data.message && (
+ <>
+
+ {results_data.message}
+
+
+ >
+ )}
+ {}}
+ />
+ >
+ )}
diff --git a/packages/orchestrator-ui-components/src/components/WfoAgent/index.ts b/packages/orchestrator-ui-components/src/components/WfoAgent/index.ts
index 1fc506036..bcd383082 100644
--- a/packages/orchestrator-ui-components/src/components/WfoAgent/index.ts
+++ b/packages/orchestrator-ui-components/src/components/WfoAgent/index.ts
@@ -1,2 +1 @@
export * from './WfoAgent';
-export * from './FilterDisplay';
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx
index 6b3485c5f..1267ccd37 100644
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx
+++ b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearch/WfoSearch.tsx
@@ -19,8 +19,6 @@ import { WfoSubscription } from '@/components';
import { WfoBadge } from '@/components/WfoBadges';
import {
ENTITY_TABS,
- findResultIndexById,
- getRecordId,
isSubscriptionSearchResult,
} from '@/components/WfoSearchPage/utils';
import { TreeProvider } from '@/contexts';
@@ -172,9 +170,8 @@ export const WfoSearch = () => {
useEffect(() => {
if (results.data.length > 0) {
if (selectedRecordId) {
- const foundIndex = findResultIndexById(
- results.data,
- selectedRecordId,
+ const foundIndex = results.data.findIndex(
+ (result) => result.entity_id === selectedRecordId,
);
if (foundIndex !== -1) {
@@ -368,9 +365,9 @@ export const WfoSearch = () => {
setSelectedRecordIndex(index);
const record = results.data[index];
if (record) {
- const recordId =
- getRecordId(record);
- setSelectedRecordId(recordId);
+ setSelectedRecordId(
+ record.entity_id,
+ );
}
}}
/>
@@ -395,8 +392,7 @@ export const WfoSearch = () => {
subscriptionId={
results.data[
selectedRecordIndex
- ].subscription
- .subscription_id
+ ].entity_id
}
/>
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx
index 59548ae05..10595da2b 100644
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx
+++ b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResultItem.tsx
@@ -13,14 +13,14 @@ import {
import { WfoBadge } from '@/components/WfoBadges';
import { useOrchestratorTheme } from '@/hooks';
-import { AnySearchResult } from '@/types';
+import { SearchResult } from '@/types';
-import { getDescription, getDetailUrl } from '../utils';
+import { getDetailUrl } from '../utils';
import { WfoHighlightedText } from './WfoHighlightedText';
import { WfoPathBreadcrumb } from './WfoPathBreadcrumb';
interface WfoSearchResultItemProps {
- result: AnySearchResult;
+ result: SearchResult;
index: number;
isSelected?: boolean;
onSelect?: () => void;
@@ -79,7 +79,7 @@ export const WfoSearchResultItem: FC = ({
fontWeight: theme.font.weight.semiBold,
}}
>
- {getDescription(result)}
+ {result.entity_title}
{matchingField && (
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResults.tsx b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResults.tsx
index eea49ed37..a456a63c5 100644
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResults.tsx
+++ b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSearchResults.tsx
@@ -1,16 +1,15 @@
-import React, { useState } from 'react';
+import React from 'react';
import { EuiFlexGroup, EuiPanel } from '@elastic/eui';
-import { AnySearchResult } from '@/types';
+import { SearchResult } from '@/types';
import { WfoSearchEmptyState } from './WfoSearchEmptyState';
import { WfoSearchLoadingState } from './WfoSearchLoadingState';
import { WfoSearchResultItem } from './WfoSearchResultItem';
-import { WfoSubscriptionDetailModal } from './WfoSubscriptionDetailModal';
interface WfoSearchResultsProps {
- results: AnySearchResult[];
+ results: SearchResult[];
loading: boolean;
selectedRecordIndex?: number;
onRecordSelect?: (index: number) => void;
@@ -22,15 +21,6 @@ export const WfoSearchResults = ({
selectedRecordIndex = 0,
onRecordSelect,
}: WfoSearchResultsProps) => {
- const [modalData, setModalData] = useState<{
- subscription: unknown;
- matchingField?: unknown;
- } | null>(null);
-
- const handleCloseModal = () => {
- setModalData(null);
- };
-
if (loading) {
return ;
}
@@ -40,26 +30,20 @@ export const WfoSearchResults = ({
}
return (
- <>
-
-
- {results.map((result, idx) => (
- onRecordSelect?.(idx)}
- />
- ))}
-
-
-
- >
+
+
+ {results.map((result, idx) => (
+ {
+ onRecordSelect?.(idx);
+ }}
+ />
+ ))}
+
+
);
};
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSubscriptionDetailModal.tsx b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSubscriptionDetailModal.tsx
deleted file mode 100644
index dc1185869..000000000
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/WfoSubscriptionDetailModal.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import React, { FC } from 'react';
-
-import { useTranslations } from 'next-intl';
-
-import {
- EuiButton,
- EuiModal,
- EuiModalBody,
- EuiModalFooter,
- EuiModalHeader,
- EuiModalHeaderTitle,
-} from '@elastic/eui';
-
-import { WfoSubscription } from '@/components';
-import { TreeProvider } from '@/contexts';
-
-interface WfoSubscriptionDetailModalProps {
- isVisible: boolean;
- onClose: () => void;
- subscriptionData: unknown | null;
- matchingField?: unknown;
-}
-
-export const WfoSubscriptionDetailModal: FC<
- WfoSubscriptionDetailModalProps
-> = ({ isVisible, onClose, subscriptionData }) => {
- const t = useTranslations('search.page');
- if (!isVisible || !subscriptionData) return null;
-
- const subscriptionId =
- subscriptionData &&
- (subscriptionData as { subscription_id: string }).subscription_id;
-
- return (
-
-
-
- {t('subscriptionDetails')}
-
-
-
-
-
-
-
-
-
-
-
- {t('closeButton')}
-
-
-
- );
-};
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/index.ts b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/index.ts
index c0977a583..5405fff3a 100644
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/index.ts
+++ b/packages/orchestrator-ui-components/src/components/WfoSearchPage/WfoSearchResults/index.ts
@@ -7,4 +7,3 @@ export * from './WfoSearchMetadataHeader';
export * from './WfoSearchPaginationInfo';
export * from './WfoHighlightedText';
export * from './WfoPathBreadcrumb';
-export * from './WfoSubscriptionDetailModal';
diff --git a/packages/orchestrator-ui-components/src/components/WfoSearchPage/utils.ts b/packages/orchestrator-ui-components/src/components/WfoSearchPage/utils.ts
index 3509d3754..616333b39 100644
--- a/packages/orchestrator-ui-components/src/components/WfoSearchPage/utils.ts
+++ b/packages/orchestrator-ui-components/src/components/WfoSearchPage/utils.ts
@@ -1,36 +1,19 @@
-import {
- AnySearchResult,
- Condition,
- EntityKind,
- Group,
- ProcessSearchResult,
- ProductSearchResult,
- SubscriptionSearchResult,
- WorkflowSearchResult,
-} from '@/types';
+import { Condition, EntityKind, Group, SearchResult } from '@/types';
-export function isSubscriptionSearchResult(
- item: AnySearchResult,
-): item is SubscriptionSearchResult {
- return 'subscription' in item && typeof item.subscription === 'object';
+export function isSubscriptionSearchResult(item: SearchResult): boolean {
+ return item.entity_type === 'SUBSCRIPTION';
}
-export function isProcessSearchResult(
- item: AnySearchResult,
-): item is ProcessSearchResult {
- return 'process' in item && typeof item.process === 'object';
+export function isProcessSearchResult(item: SearchResult): boolean {
+ return item.entity_type === 'PROCESS';
}
-export function isProductSearchResult(
- item: AnySearchResult,
-): item is ProductSearchResult {
- return 'product' in item && typeof item.product === 'object';
+export function isProductSearchResult(item: SearchResult): boolean {
+ return item.entity_type === 'PRODUCT';
}
-export function isWorkflowSearchResult(
- item: AnySearchResult,
-): item is WorkflowSearchResult {
- return 'workflow' in item && typeof item.workflow === 'object';
+export function isWorkflowSearchResult(item: SearchResult): boolean {
+ return item.entity_type === 'WORKFLOW';
}
export const isCondition = (item: Group | Condition): item is Condition => {
@@ -48,92 +31,9 @@ export const getEndpointPath = (entityType: EntityKind): string => {
return ENDPOINT_PATHS[entityType] || ENDPOINT_PATHS.SUBSCRIPTION;
};
-export const getDisplayText = (item: AnySearchResult): string => {
- if (isSubscriptionSearchResult(item)) {
- return item.subscription.description || 'Subscription';
- }
- if (isProcessSearchResult(item)) {
- return item.process.workflowName;
- }
- if (isProductSearchResult(item)) {
- return item.product.name;
- }
- if (isWorkflowSearchResult(item)) {
- return item.workflow.name;
- }
- return 'Unknown result type';
-};
-
-export const getRecordId = (result: AnySearchResult): string => {
- if (isSubscriptionSearchResult(result)) {
- return result.subscription.subscription_id;
- }
- if (isProductSearchResult(result)) {
- return result.product.product_id;
- }
- if (isProcessSearchResult(result)) {
- return result.process.processId;
- }
- if (isWorkflowSearchResult(result)) {
- return result.workflow.name;
- }
- return '';
-};
-
-export const findResultIndexById = (
- results: AnySearchResult[],
- recordId: string,
-): number => {
- return results.findIndex((result) => {
- if (isSubscriptionSearchResult(result)) {
- return result.subscription.subscription_id === recordId;
- }
- if (isProductSearchResult(result)) {
- return result.product.product_id === recordId;
- }
- if (isProcessSearchResult(result)) {
- return result.process.processId === recordId;
- }
- if (isWorkflowSearchResult(result)) {
- return result.workflow.name === recordId;
- }
- return false;
- });
-};
-
-export const getDetailUrl = (
- result: AnySearchResult,
- baseUrl: string,
-): string => {
- if (isSubscriptionSearchResult(result)) {
- return `${baseUrl}/subscriptions/${result.subscription.subscription_id}`;
- }
- if (isProductSearchResult(result)) {
- return `${baseUrl}/products/${result.product.product_id}`;
- }
- if (isProcessSearchResult(result)) {
- return `${baseUrl}/processes/${result.process.processId}`;
- }
- if (isWorkflowSearchResult(result)) {
- return `${baseUrl}/workflows/${result.workflow.name}`;
- }
- return '#';
-};
-
-export const getDescription = (result: AnySearchResult): string => {
- if (isSubscriptionSearchResult(result)) {
- return result.subscription.description;
- }
- if (isProductSearchResult(result)) {
- return result.product.description || result.product.name;
- }
- if (isWorkflowSearchResult(result)) {
- return result.workflow.description || result.workflow.name;
- }
- if (isProcessSearchResult(result)) {
- return result.process.workflowName;
- }
- return 'Unknown';
+export const getDetailUrl = (result: SearchResult, baseUrl: string): string => {
+ const endpointPath = getEndpointPath(result.entity_type);
+ return `${baseUrl}/${endpointPath}/${result.entity_id}`;
};
export const ENTITY_TABS = [
diff --git a/packages/orchestrator-ui-components/src/hooks/useSearchPagination.ts b/packages/orchestrator-ui-components/src/hooks/useSearchPagination.ts
index f609900f8..63596b062 100644
--- a/packages/orchestrator-ui-components/src/hooks/useSearchPagination.ts
+++ b/packages/orchestrator-ui-components/src/hooks/useSearchPagination.ts
@@ -5,15 +5,15 @@ import { Query } from '@elastic/eui';
import { buildSearchParams } from '@/components/WfoSearchPage/utils';
import { useSearchWithPaginationMutation } from '@/rtk/endpoints';
import {
- AnySearchResult,
EntityKind,
Group,
PaginatedSearchResults,
+ SearchResult,
} from '@/types';
interface PageHistoryItem {
page: number;
- results: AnySearchResult[];
+ results: SearchResult[];
cursor: number | null;
}
diff --git a/packages/orchestrator-ui-components/src/messages/en-GB.json b/packages/orchestrator-ui-components/src/messages/en-GB.json
index 697b39895..659cc67fd 100644
--- a/packages/orchestrator-ui-components/src/messages/en-GB.json
+++ b/packages/orchestrator-ui-components/src/messages/en-GB.json
@@ -475,7 +475,6 @@
"title": "Search results",
"page": {
"filledParameters": "Filled parameters",
- "results": "Results",
"emptyGroup": "Empty group",
"searchQuery": "Search query",
"activeFilters": "Active filters",
@@ -484,7 +483,7 @@
"action": "Action",
"copilot": {
"title": "Database assistant",
- "initial": "Ask me things such as:\n• *Find active subscriptions for Surf*\n• *Show terminated workflows”*\n\nThe filled template and results will appear on the left."
+ "initial": "Ask me things such as:\n• *Find active subscriptions*\n• *Show terminated workflows”*\n\nThe filled template and results will appear on the left."
}
}
},
@@ -521,7 +520,6 @@
"resultsOnPage": "{resultCount} result(s) on this page",
"searchResultsPagination": "Search results pagination",
"viewDetails": "View details",
- "closeButton": "Close",
"selectOrEnterValue": "Select or type value",
"enterValue": "Enter value",
"fromNumber": "From",
diff --git a/packages/orchestrator-ui-components/src/rtk/endpoints/agentExport.ts b/packages/orchestrator-ui-components/src/rtk/endpoints/agentExport.ts
new file mode 100644
index 000000000..c6326b19b
--- /dev/null
+++ b/packages/orchestrator-ui-components/src/rtk/endpoints/agentExport.ts
@@ -0,0 +1,23 @@
+import { BaseQueryTypes, orchestratorApi } from '@/rtk';
+import { GraphQLPageInfo } from '@/types';
+
+export type AgentExportResponse = {
+ page: object[];
+ pageInfo?: GraphQLPageInfo;
+};
+
+const agentExportApi = orchestratorApi.injectEndpoints({
+ endpoints: (builder) => ({
+ getAgentExport: builder.query({
+ query: (downloadUrl) => ({
+ url: downloadUrl,
+ method: 'GET',
+ }),
+ extraOptions: {
+ baseQueryType: BaseQueryTypes.fetch,
+ },
+ }),
+ }),
+});
+
+export const { useLazyGetAgentExportQuery } = agentExportApi;
diff --git a/packages/orchestrator-ui-components/src/types/search.ts b/packages/orchestrator-ui-components/src/types/search.ts
index 01ab9d06c..04b3c395b 100644
--- a/packages/orchestrator-ui-components/src/types/search.ts
+++ b/packages/orchestrator-ui-components/src/types/search.ts
@@ -1,85 +1,23 @@
export type EntityKind = 'SUBSCRIPTION' | 'PRODUCT' | 'WORKFLOW' | 'PROCESS';
-export interface SubscriptionMatchingField {
+export interface MatchingField {
text: string;
path: string;
highlight_indices: [number, number][];
}
-export interface SubscriptionSearchResult {
+export interface SearchResult {
+ entity_id: string;
+ entity_type: EntityKind;
+ entity_title: string;
score: number;
perfect_match: number;
- matching_field?: SubscriptionMatchingField | null;
- subscription: {
- subscription_id: string;
- description: string;
- product: {
- name: string;
- description: string;
- };
- };
+ matching_field?: MatchingField | null;
}
-export interface ProcessSearchResult {
- score: number;
- perfect_match: number;
- matching_field?: SubscriptionMatchingField | null;
- process: {
- processId: string;
- workflowName: string;
- workflowId: string;
- status: string;
- isTask: boolean;
- createdBy?: string | null;
- startedAt: string;
- lastModifiedAt: string;
- lastStep?: string | null;
- failedReason?: string | null;
- subscriptionIds?: string[] | null;
- };
-}
-
-export interface ProductSearchResult {
- score: number;
- perfect_match: number;
- matching_field?: SubscriptionMatchingField | null;
- product: {
- product_id: string;
- name: string;
- product_type: string;
- tag?: string | null;
- description?: string | null;
- status?: string | null;
- created_at?: string | null;
- };
-}
-
-export interface WorkflowSearchResult {
- score: number;
- perfect_match: number;
- matching_field?: SubscriptionMatchingField | null;
- workflow: {
- name: string;
- products: {
- product_type: string;
- product_id: string;
- name: string;
- }[];
- description?: string | null;
- created_at?: string | null;
- };
-}
-
-/** Union of all search results */
-export type AnySearchResult =
- | SubscriptionSearchResult
- | ProcessSearchResult
- | ProductSearchResult
- | WorkflowSearchResult;
-
/** Paginated search results */
export type PaginatedSearchResults = {
- data: AnySearchResult[];
+ data: SearchResult[];
page_info: {
has_next_page: boolean;
next_page_cursor: number | null;
@@ -138,7 +76,7 @@ type ActionType = 'select';
type BaseSearchParameters = {
query?: string | null;
- filters?: PathFilter[] | null;
+ filters?: PathFilter[] | Group | null;
action: ActionType;
};
diff --git a/version-compatibility.json b/version-compatibility.json
index 32a061769..94766f651 100644
--- a/version-compatibility.json
+++ b/version-compatibility.json
@@ -1,4 +1,9 @@
[
+ {
+ "orchestratorUiVersion": "6.5.0",
+ "minimumOrchestratorCoreVersion": "4.6.0",
+ "changes": "To support new functionality in the FE for the Agent and LLM, the BE endpoints have changed."
+ },
{
"orchestratorUiVersion": "5.3.5",
"minimumOrchestratorCoreVersion": "4.2.0",