Tools: render fragments

This commit is contained in:
Enrico Ros
2024-10-16 16:55:21 -07:00
parent ddee6aecfb
commit c3c65ea3d3
4 changed files with 169 additions and 116 deletions
@@ -0,0 +1,104 @@
import * as React from 'react';
import type { ColorPaletteProp, SxProps, VariantProp } from '@mui/joy/styles/types';
import { Sheet } from '@mui/joy';
import { BlocksContainer } from '~/modules/blocks/BlocksContainers';
import { useScaledTypographySx } from '~/modules/blocks/blocks.styles';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageToolInvocationPart } from '~/common/stores/chat/chat.fragments';
const keyValueGridSx = {
border: '1px solid',
borderRadius: 'sm',
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
p: 1.5,
// Grid layout with 2 columns
display: 'grid',
gridTemplateColumns: 'auto 1fr',
// alignItems: 'baseline',
columnGap: 2,
rowGap: 0.5,
// fade the text of the first column
// '& > :nth-of-type(odd)': {
// opacity: 0.67,
// // fontSize: '90%',
// },
} as const;
export type KeyValueData = { label: string, value: React.ReactNode, asCode?: boolean }[];
export function KeyValueGrid(props: {
data: KeyValueData,
contentScaling: ContentScaling,
color?: ColorPaletteProp,
variant?: VariantProp,
stableSx?: SxProps,
}) {
const { fontSize, lineHeight } = useScaledTypographySx(props.contentScaling, false, false);
const gridSx = React.useMemo(() => ({
...keyValueGridSx,
// fontWeight,
fontSize,
lineHeight,
...props.stableSx,
}), [fontSize, lineHeight, props.stableSx]);
return (
<Sheet color={props.color} variant={props.variant || 'soft'} sx={gridSx}>
{props.data.map(({ label, value }, index) => (
<React.Fragment key={index}>
<div>{label}</div>
<div>{value}</div>
</React.Fragment>
))}
</Sheet>
);
}
export function BlockPartToolInvocation(props: {
toolInvocationPart: DMessageToolInvocationPart,
contentScaling: ContentScaling,
onDoubleClick?: (event: React.MouseEvent) => void;
}) {
const part = props.toolInvocationPart;
const kvData: KeyValueData = React.useMemo(() => {
switch (part.invocation.type) {
case 'function_call':
return [
{ label: 'Name', value: <strong>{part.invocation.name}</strong> },
{ label: 'Args', value: part.invocation.args || 'None', asCode: true },
{ label: 'Id', value: part.id },
];
case 'code_execution':
return [
{ label: 'Language', value: part.invocation.language },
{ label: 'Author', value: part.invocation.author },
{
label: 'Code',
value: <div style={{ whiteSpace: 'pre-wrap' }}>{part.invocation.code.trim()}</div>,
},
{ label: 'Id', value: part.id },
];
}
}, [part]);
return (
<BlocksContainer onDoubleClick={props.onDoubleClick}>
<KeyValueGrid
data={kvData}
contentScaling={props.contentScaling}
/>
</BlocksContainer>
);
}
@@ -0,0 +1,49 @@
import * as React from 'react';
import { BlocksContainer } from '~/modules/blocks/BlocksContainers';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageToolResponsePart } from '~/common/stores/chat/chat.fragments';
import { KeyValueData, KeyValueGrid } from './BlockPartToolInvocation';
export function BlockPartToolResponse(props: {
toolResponsePart: DMessageToolResponsePart,
contentScaling: ContentScaling,
onDoubleClick?: (event: React.MouseEvent) => void;
}) {
const part = props.toolResponsePart;
const kvData: KeyValueData = React.useMemo(() => {
switch (part.response.type) {
case 'function_call':
return [
{ label: 'Id', value: part.id },
{ label: 'Name', value: <strong>{part.response.name}</strong> },
{ label: 'Response', value: part.response.result, asCode: true },
...(!part.error ? [] : [{ label: 'Error', value: part.error }]),
{ label: 'Environment', value: part.environment },
];
case 'code_execution':
return [
{ label: 'Id', value: part.id },
{ label: 'Response', value: part.response.result, asCode: true },
...(!part.error ? [] : [{ label: 'Error', value: part.error }]),
{ label: 'Executor', value: part.response.executor },
{ label: 'Environment', value: part.environment },
];
}
}, [part]);
return (
<BlocksContainer onDoubleClick={props.onDoubleClick}>
<KeyValueGrid
data={kvData}
contentScaling={props.contentScaling}
color={part.error ? 'danger' : 'primary'}
/>
</BlocksContainer>
);
}
@@ -1,9 +1,7 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, Button, Sheet } from '@mui/joy';
import { BlocksContainer } from '~/modules/blocks/BlocksContainers';
import { Box, Button } from '@mui/joy';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling, UIComplexityMode } from '~/common/app.theme';
@@ -17,6 +15,8 @@ import { BlockPartError } from './BlockPartError';
import { BlockPartImageRef } from './BlockPartImageRef';
import { BlockPartPlaceholder } from './BlockPartPlaceholder';
import { BlockPartText_AutoBlocks } from './BlockPartText_AutoBlocks';
import { BlockPartToolInvocation } from './BlockPartToolInvocation';
import { BlockPartToolResponse } from './BlockPartToolResponse';
const editLayoutSx: SxProps = {
@@ -234,122 +234,22 @@ export function ContentFragments(props: {
case 'tool_invocation':
return (
<BlocksContainer key={fId}>
{part.invocation.type === 'function_call' ? (
<Sheet color='neutral' variant='soft' sx={{
flex: 1,
border: '1px solid',
borderColor: 'neutral.outlinedColor',
width: '100%',
borderRadius: 'lg',
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
fontSize: 'sm',
p: 2,
// grid layout with 2 cols
display: 'grid',
gridTemplateColumns: 'auto 1fr',
columnGap: 2,
rowGap: 1,
}}>
<div>Id</div>
<div>{part.id}</div>
<div>Name</div>
<div>{part.invocation.name}</div>
<div>Args</div>
<div>{part.invocation.args/*?.replaceAll('{', '').replaceAll('}', '').replaceAll('","', '", "')*/}</div>
</Sheet>
) : (
<Sheet color='neutral' variant='soft' sx={{
flex: 1,
border: '1px solid',
borderColor: 'neutral.outlinedColor',
width: '100%',
borderRadius: 'lg',
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
fontSize: 'sm',
p: 2,
// grid layout with 2 cols
display: 'grid',
gridTemplateColumns: 'auto 1fr',
columnGap: 2,
rowGap: 1,
}}>
<div>Id</div>
<div>{part.id}</div>
<div>Language</div>
<div>{part.invocation.language}</div>
<div>Code</div>
<div style={{ whiteSpace: 'pre' }}>{part.invocation.code?.trim()}</div>
<div>Author</div>
<div>{part.invocation.author}</div>
</Sheet>
)}
</BlocksContainer>
<BlockPartToolInvocation
key={fId}
toolInvocationPart={part}
contentScaling={props.contentScaling}
onDoubleClick={props.onDoubleClick}
/>
);
case 'tool_response':
return (
<BlocksContainer key={fId}>
{part.response.type === 'function_call' ? (
<Sheet color='neutral' variant='soft' sx={{
flex: 1,
border: '1px solid',
borderColor: 'neutral.outlinedColor',
width: '100%',
borderRadius: 'lg',
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
fontSize: 'sm',
p: 2,
// grid layout with 2 cols
display: 'grid',
gridTemplateColumns: 'auto 1fr',
columnGap: 2,
rowGap: 1,
}}>
<div>Type</div>
<div>Function Call Response</div>
<div>Id</div>
<div>{part.id}</div>
<div>Error</div>
<div>{part.error === null ? 'null' : part.error === 'false' ? '' : part.error}</div>
<div>Name</div>
<div style={{ fontWeight: 700 }}>{part.response.name}</div>
<div>Result</div>
<div style={{ fontWeight: 700 }}>{part.response.result}</div>
<div>Environment</div>
<div>{part.environment}</div>
</Sheet>
) : (
<Sheet color='neutral' variant='solid' sx={{
flex: 1,
border: '1px solid',
borderColor: 'neutral.outlinedColor',
width: '100%',
borderRadius: 'lg',
boxShadow: 'inset 2px 0 4px -2px rgba(0, 0, 0, 0.2)',
fontSize: 'sm',
p: 2,
// grid layout with 2 cols
display: 'grid',
gridTemplateColumns: 'auto 1fr',
columnGap: 2,
rowGap: 1,
}}>
<div>Type</div>
<div>Code Execution Response</div>
<div>Id</div>
<div>{part.id}</div>
<div>Error</div>
<div>{part.error === null ? 'null' : part.error === 'false' ? '' : part.error}</div>
<div>Result</div>
<div style={{ fontWeight: 700 }}>{part.response.result}</div>
<div>Executor</div>
<div>{part.response.executor}</div>
<div>Environment</div>
<div>{part.environment}</div>
</Sheet>
)}
</BlocksContainer>
<BlockPartToolResponse
key={fId}
toolResponsePart={part}
contentScaling={props.contentScaling}
onDoubleClick={props.onDoubleClick}
/>
);
case '_pt_sentinel':
+1 -1
View File
@@ -41,7 +41,7 @@ export function useScaledImageSx(contentScaling: ContentScaling): SxProps {
}), [contentScaling]);
}
export function useScaledTypographySx(contentScaling: ContentScaling, showAsDanger: boolean, showAsItalic: boolean): SxProps {
export function useScaledTypographySx(contentScaling: ContentScaling, showAsDanger: boolean, showAsItalic: boolean) {
return React.useMemo(() => ({
fontSize: themeScalingMap[contentScaling]?.blockFontSize ?? undefined,
lineHeight: themeScalingMap[contentScaling]?.blockLineHeight ?? 1.75,