From 6d47b6024ab78634cec63f8e9e643bcde76d5ccb Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Wed, 25 Sep 2024 01:40:58 -0700 Subject: [PATCH] Charts: User Fix. --- src/modules/aifn/agicodefixup/agiFixupCode.ts | 12 ++-- .../client/aix.client.fromSimpleFunction.ts | 6 +- src/modules/blocks/AutoBlocksRenderer.tsx | 2 +- src/modules/blocks/code/RenderCode.tsx | 2 +- .../code/code-renderers/RenderCodeChartJS.tsx | 63 +++++++++++++++++-- src/modules/persona/pmix/pmix.ts | 4 +- 6 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/modules/aifn/agicodefixup/agiFixupCode.ts b/src/modules/aifn/agicodefixup/agiFixupCode.ts index 521508180..ed598e190 100644 --- a/src/modules/aifn/agicodefixup/agiFixupCode.ts +++ b/src/modules/aifn/agicodefixup/agiFixupCode.ts @@ -22,21 +22,21 @@ const CodeFixes: Record = { // See `RenderCodeChartJS` 'chartjs-issue': { - description: 'Provides the corrected ChartJS configuration code.', - systemMessage: `You are an AI assistant that helps fix invalid ChartJS configuration JSON code. -When provided with invalid ChartJS code, you analyze it, identify errors, and output a corrected version in valid JSON format. + description: 'Provides the corrected Chart.js configuration code.', + systemMessage: `You are an AI assistant that helps fix invalid Chart.js configuration JSON code. +When provided with invalid Chart.js code, you analyze it, identify errors, and output a corrected version in valid JSON format. Respond only by calling the \`{{functionName}}\` function.`, - userInstructionTemplate: `The following ChartJS configuration code is invalid and cannot be parsed: + userInstructionTemplate: `The following Chart.js configuration code is invalid and cannot be parsed: \`\`\`json {{codeToFix}} \`\`\` {{errorMessageSection}} -Please analyze the code, correct any errors, and provide a valid JSON configuration that can be parsed by ChartJS. +Please analyze the code, correct any errors, and provide a valid JSON configuration that can be parsed by Chart.js. Call the function \`{{functionName}}\` once, providing the corrected code.`, functionName: 'provide_corrected_chartjs_code', outputSchema: z.object({ - corrected_code: z.string().describe('The corrected ChartJS configuration code in valid JSON format.'), + corrected_code: z.string().describe('The corrected Chart.js configuration code in valid JSON format.'), }), }, diff --git a/src/modules/aix/client/aix.client.fromSimpleFunction.ts b/src/modules/aix/client/aix.client.fromSimpleFunction.ts index b8870b410..f48a7aefd 100644 --- a/src/modules/aix/client/aix.client.fromSimpleFunction.ts +++ b/src/modules/aix/client/aix.client.fromSimpleFunction.ts @@ -70,13 +70,13 @@ export function aixRequireSingleFunctionCallInvocation(fragments: DMessageConten if (!Array.isArray(fragments) || fragments.length !== 1) { if (AIX_DEBUG_CLIENT_TOOLS) console.error('[DEV] single-function-call: invalid fragments:', fragments, 'for', debugLabel); - throw new Error('AIX: Unexpected response for ' + debugLabel); + throw new Error('AIX: Unexpected response.'); } if (!isContentFragment(fragments[0]) || fragments[0].part.pt !== 'tool_invocation') { if (AIX_DEBUG_CLIENT_TOOLS) console.error('[DEV] single-function-call: invalid fragment part:', fragments[0].part, 'for', debugLabel); - throw new Error('AIX: Missing tool invocation for ' + debugLabel); + throw new Error('AIX: Missing tool invocation.'); } const { invocation } = fragments[0].part; @@ -84,7 +84,7 @@ export function aixRequireSingleFunctionCallInvocation(fragments: DMessageConten if (invocation.type !== 'function_call' || invocation.name !== expectedFunctionName) { if (AIX_DEBUG_CLIENT_TOOLS) console.error('[DEV] single-function-call: invalid invocation:', invocation, 'for', debugLabel); - throw new Error('AIX: Expected a function call to ' + expectedFunctionName + ' for ' + debugLabel); + throw new Error('AIX: Expected a function call.'); } if (!invocation.args) { diff --git a/src/modules/blocks/AutoBlocksRenderer.tsx b/src/modules/blocks/AutoBlocksRenderer.tsx index 2588a3846..f7a4cd0b7 100644 --- a/src/modules/blocks/AutoBlocksRenderer.tsx +++ b/src/modules/blocks/AutoBlocksRenderer.tsx @@ -146,7 +146,7 @@ export function AutoBlocksRenderer(props: { let enhancedStartCollapsed = false; if (bkInput.title === BLOCK_CODE_VND_AGI_CHARTJS) { disableEnhancedRender = false; - // For ChartJS charts, at the moment, we use the 'unwanted' refresh at the end of the message to start (that block) without collapse + // For Chart.js charts, at the moment, we use the 'unwanted' refresh at the end of the message to start (that block) without collapse enhancedStartCollapsed = bkInput.isPartial; } diff --git a/src/modules/blocks/code/RenderCode.tsx b/src/modules/blocks/code/RenderCode.tsx index 0c1dbb337..b3b06f32e 100644 --- a/src/modules/blocks/code/RenderCode.tsx +++ b/src/modules/blocks/code/RenderCode.tsx @@ -269,7 +269,7 @@ function RenderCodeImpl(props: RenderCodeBaseProps & { )} - {/* SVG, ChartJS, Mermaid, PlantUML -- including a max-out button */} + {/* SVG, Chart.js, Mermaid, PlantUML -- including a max-out button */} {(isSVGCode || isChartJSCode || isMermaidCode || isPlantUMLCode) && ( {/* Toggle rendering */} diff --git a/src/modules/blocks/code/code-renderers/RenderCodeChartJS.tsx b/src/modules/blocks/code/code-renderers/RenderCodeChartJS.tsx index 33637f8b7..496183736 100644 --- a/src/modules/blocks/code/code-renderers/RenderCodeChartJS.tsx +++ b/src/modules/blocks/code/code-renderers/RenderCodeChartJS.tsx @@ -1,7 +1,10 @@ import * as React from 'react'; import type { SxProps } from '@mui/joy/styles/types'; -import { Box, Typography } from '@mui/joy'; +import { Box, Button, Typography } from '@mui/joy'; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; + +import { useAgiFixupCode } from '~/modules/aifn/agicodefixup/useAgiFixupCode'; import { ChartConstructorType, fixupChartJSObject, useDynamicChartJS } from './useDynamicChartJS'; @@ -29,6 +32,7 @@ export function RenderCodeChartJS(props: { // state const [renderError, setRenderError] = React.useState(null); + const [fixupError, setFixupError] = React.useState(null); const canvasRef = React.useRef(null); const chartInstanceRef = React.useRef(null); @@ -43,10 +47,13 @@ export function RenderCodeChartJS(props: { fixupChartJSObject(config); return { chartConfig: config, parseError: null }; } catch (error: any) { - return { chartConfig: null, parseError: 'Invalid Chart.js input: ' + (error.message || 'Unknown error.') }; + return { chartConfig: null, parseError: error.message as string || 'Unknown error.' }; } }, [props.chartJSCode]); + // AI functions + const { isFetching, refetch } = useAgiFixupCode('chartjs-issue', false, props.chartJSCode, parseResult.parseError); + // Rendering React.useEffect(() => { @@ -72,6 +79,24 @@ export function RenderCodeChartJS(props: { }, [chartJS, parseResult.chartConfig]); + // handlers + + const { onReplaceInCode } = props; + + const handleChartRegenerate = React.useCallback(async () => { + if (!onReplaceInCode) return; + setFixupError(null); + refetch().then((result) => { + if (result.data) + onReplaceInCode(props.chartJSCode, result.data); + else if (result.error) + setFixupError(result.error.message || 'Unknown error.'); + else + setFixupError('Unknown Fixup error.'); + }); + }, [onReplaceInCode, props.chartJSCode, refetch]); + + // Handle all the non-chart states switch (true) { case isLibraryLoading: @@ -80,8 +105,38 @@ export function RenderCodeChartJS(props: { return null; case !!loadingError: return {loadingError}; - case !!parseResult.parseError: - return {parseResult.parseError}; + case !!parseResult.parseError || !!fixupError: + return ( + + {props.onReplaceInCode && ( + + )} + {fixupError ? ( + + Error fixing chart: {fixupError} + + ) : (parseResult.parseError && !isFetching) && ( + + Invalid Chart.js input: {parseResult.parseError} + + )} + + ); case !!renderError: return {renderError}; } diff --git a/src/modules/persona/pmix/pmix.ts b/src/modules/persona/pmix/pmix.ts index e98ceeefc..fccb77ef2 100644 --- a/src/modules/persona/pmix/pmix.ts +++ b/src/modules/persona/pmix/pmix.ts @@ -84,10 +84,10 @@ export function bareBonesPromptMixer(_template: string, assistantLlmId: DLLMId | mixed = mixed.replace('{{PreferTables}}', 'Data presentation: prefer tables (auto-columns)'); // {{Render...}} mixed = mixed.replace('{{RenderChartJS}}', ` -When presenting data that would be better visualized as a chart, output a ChartJS configuration object in this format: +When presenting data that would be better visualized as a chart, output a Chart.js configuration object in this format: \`\`\`chartjs { - // Valid and complete ChartJS configuration JSON (DO NOT USE FUNCTIONS) + // Valid and complete Chart.js configuration JSON (DO NOT USE FUNCTIONS) } \`\`\` Choose the most suitable chart type based on the data and context. Include only the JSON configuration, without any explanatory text. Ensure the JSON is valid and complete.