21c51ec251
## Summary - **Fix token renewal endpoint**: Use `/metadata` instead of `/graphql` for token renewal in agent chat, fixing auth issues - **Improve tool display**: Add `load_skills` support, show formatted tool names (underscores → spaces) with finish/loading states, display tool icons during loading, and support custom loading messages from tool input - **Refactor workflow agent management**: Replace direct `AgentRepository` access with `AgentService` for create/delete/find operations in workflow steps, improving encapsulation and consistency - **Simplify Apollo client usage**: Remove explicit Apollo client override in `useGetToolIndex`, add `AgentChatProvider` to `AppRouterProviders` - **Fix load-skill tool**: Change parameter type from `string` to `json` for proper schema parsing - **Update agent-chat-streaming**: Use `AgentService` for agent resolution and tool registration instead of direct repository queries ## Test plan - [ ] Verify AI agent chat works end-to-end (send message, receive response) - [ ] Verify tool steps display correctly with icons and proper messages during loading and after completion - [ ] Verify workflow AI agent step creation and deletion works correctly - [ ] Verify workflow version cloning preserves agent configuration - [ ] Verify token renewal works when tokens expire during agent chat Made with [Cursor](https://cursor.com) --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
189 lines
5.4 KiB
TypeScript
189 lines
5.4 KiB
TypeScript
import { useLazyQuery } from '@apollo/client';
|
|
import styled from '@emotion/styled';
|
|
import { t } from '@lingui/core/macro';
|
|
import { useState } from 'react';
|
|
|
|
import { GET_TOOL_INPUT_SCHEMA } from '@/ai/graphql/queries/getToolInputSchemas';
|
|
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
|
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
|
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import {
|
|
IconChevronDown,
|
|
IconChevronRight,
|
|
IconCode,
|
|
IconDatabase,
|
|
IconPlayerPlay,
|
|
IconSettings,
|
|
IconTable,
|
|
OverflowingTextWithTooltip,
|
|
} from 'twenty-ui/display';
|
|
import { JsonTree } from 'twenty-ui/json-visualizer';
|
|
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
|
|
|
|
import { type JsonValue } from 'type-fest';
|
|
|
|
import { useCopyToClipboard } from '~/hooks/useCopyToClipboard';
|
|
|
|
type GetToolInputSchemaQuery = {
|
|
getToolInputSchema: object | null;
|
|
};
|
|
|
|
export type SystemTool = {
|
|
name: string;
|
|
description: string;
|
|
category: string;
|
|
objectName?: string;
|
|
};
|
|
|
|
export type SettingsSystemToolTableRowProps = {
|
|
tool: SystemTool;
|
|
};
|
|
|
|
export const StyledSystemToolTableRow = styled(TableRow)<{
|
|
isExpandable?: boolean;
|
|
}>`
|
|
grid-template-columns: 1fr 100px 36px;
|
|
opacity: 0.7;
|
|
cursor: ${({ isExpandable }) => (isExpandable ? 'pointer' : 'default')};
|
|
|
|
&:hover {
|
|
opacity: ${({ isExpandable }) => (isExpandable ? 0.85 : 0.7)};
|
|
}
|
|
`;
|
|
|
|
const StyledNameTableCell = styled(TableCell)`
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
gap: ${({ theme }) => theme.spacing(2)};
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
`;
|
|
|
|
const StyledIconContainer = styled.div`
|
|
align-items: center;
|
|
display: flex;
|
|
flex-shrink: 0;
|
|
`;
|
|
|
|
const StyledActionTableCell = styled(TableCell)`
|
|
justify-content: flex-end;
|
|
padding-right: ${({ theme }) => theme.spacing(2)};
|
|
`;
|
|
|
|
const StyledExpandableContent = styled.div`
|
|
background-color: ${({ theme }) => theme.background.secondary};
|
|
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
|
padding: ${({ theme }) => theme.spacing(4)};
|
|
`;
|
|
|
|
const StyledSectionTitle = styled.div`
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
font-size: ${({ theme }) => theme.font.size.sm};
|
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
|
`;
|
|
|
|
const StyledDescription = styled.div`
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
font-size: ${({ theme }) => theme.font.size.sm};
|
|
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
|
`;
|
|
|
|
const getCategoryIcon = (category: string) => {
|
|
switch (category) {
|
|
case 'database':
|
|
return IconDatabase;
|
|
case 'workflow':
|
|
return IconPlayerPlay;
|
|
case 'metadata':
|
|
return IconSettings;
|
|
case 'view':
|
|
return IconTable;
|
|
default:
|
|
return IconCode;
|
|
}
|
|
};
|
|
|
|
export const SettingsSystemToolTableRow = ({
|
|
tool,
|
|
}: SettingsSystemToolTableRowProps) => {
|
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
const { copyToClipboard } = useCopyToClipboard();
|
|
|
|
// Fetch inputSchema for this specific tool on first expand
|
|
const [fetchSchema, { data: schemaData, loading: schemaLoading }] =
|
|
useLazyQuery<GetToolInputSchemaQuery>(GET_TOOL_INPUT_SCHEMA, {
|
|
variables: { toolName: tool.name },
|
|
});
|
|
|
|
const inputSchema = schemaData?.getToolInputSchema;
|
|
|
|
const hasInputSchema =
|
|
isDefined(inputSchema) && Object.keys(inputSchema).length > 0;
|
|
|
|
const Icon = getCategoryIcon(tool.category);
|
|
|
|
const handleRowClick = () => {
|
|
if (!schemaData && !schemaLoading) {
|
|
fetchSchema();
|
|
}
|
|
setIsExpanded(!isExpanded);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<StyledSystemToolTableRow
|
|
key={tool.name}
|
|
onClick={handleRowClick}
|
|
isExpandable
|
|
>
|
|
<StyledNameTableCell>
|
|
<StyledIconContainer>
|
|
<Icon size={16} />
|
|
</StyledIconContainer>
|
|
<OverflowingTextWithTooltip text={tool.name} />
|
|
</StyledNameTableCell>
|
|
<TableCell>
|
|
<SettingsItemTypeTag item={{ isCustom: false }} />
|
|
</TableCell>
|
|
<StyledActionTableCell>
|
|
{isExpanded ? (
|
|
<IconChevronDown size={16} />
|
|
) : (
|
|
<IconChevronRight size={16} />
|
|
)}
|
|
</StyledActionTableCell>
|
|
</StyledSystemToolTableRow>
|
|
|
|
<AnimatedExpandableContainer isExpanded={isExpanded} mode="fit-content">
|
|
<StyledExpandableContent>
|
|
{tool.description && (
|
|
<StyledDescription>{tool.description}</StyledDescription>
|
|
)}
|
|
{schemaLoading && (
|
|
<StyledSectionTitle>{t`Loading schema...`}</StyledSectionTitle>
|
|
)}
|
|
{hasInputSchema && (
|
|
<>
|
|
<StyledSectionTitle>{t`Input Schema`}</StyledSectionTitle>
|
|
<JsonTree
|
|
value={inputSchema as JsonValue}
|
|
shouldExpandNodeInitially={() => true}
|
|
emptyArrayLabel={t`Empty Array`}
|
|
emptyObjectLabel={t`No parameters`}
|
|
emptyStringLabel={t`[empty string]`}
|
|
arrowButtonCollapsedLabel={t`Expand`}
|
|
arrowButtonExpandedLabel={t`Collapse`}
|
|
onNodeValueClick={copyToClipboard}
|
|
/>
|
|
</>
|
|
)}
|
|
{!schemaLoading && !hasInputSchema && (
|
|
<StyledSectionTitle>{t`No parameters`}</StyledSectionTitle>
|
|
)}
|
|
</StyledExpandableContent>
|
|
</AnimatedExpandableContainer>
|
|
</>
|
|
);
|
|
};
|