Drop the charts module as-is, will fix later.

This commit is contained in:
Enrico Ros
2024-09-24 10:03:15 -07:00
parent 1236d7c1ac
commit 6fc5acfeb9
6 changed files with 200 additions and 5 deletions
+1
View File
@@ -86,6 +86,7 @@
"@types/react-katex": "^3.0.4",
"@types/react-timeago": "^4.1.7",
"@types/turndown": "^5.0.5",
"chart.js": "^4.4.4",
"eslint": "^8.57.1",
"eslint-config-next": "^14.2.13",
"prettier": "^3.3.3",
+1
View File
@@ -100,6 +100,7 @@ Current date: {{LocaleNow}}
{{RenderHTML}}
{{RenderSVG}}
{{PreferTables}}
{{RenderChartJS}}
`.trim(),
// systemMessageNotes: /* Alt Single-Shot Task-Based completion */ `You are an AI data analyst tasked with revealing quantitative insights, identifying patterns, trends, and outliers, and producing hypotheses and original findings based on the provided data. Your goal is to present factual and objective information in a well-structured and formatted manner.
//
+25 -5
View File
@@ -3,6 +3,7 @@ import { useShallow } from 'zustand/react/shallow';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, ButtonGroup, Sheet, Typography } from '@mui/joy';
import BarChartIcon from '@mui/icons-material/BarChart';
import ChangeHistoryTwoToneIcon from '@mui/icons-material/ChangeHistoryTwoTone';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import FitScreenIcon from '@mui/icons-material/FitScreen';
@@ -15,6 +16,7 @@ import { copyToClipboard } from '~/common/util/clipboardUtils';
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { BUTTON_RADIUS, OverlayButton, overlayButtonsActiveSx, overlayButtonsClassName, overlayButtonsTopRightSx, overlayGroupWithShadowSx } from '../OverlayButton';
import { RenderCodeChartJS } from './code-renderers/RenderCodeChartJS';
import { RenderCodeHtmlIFrame } from './code-renderers/RenderCodeHtmlIFrame';
import { RenderCodeMermaid } from './code-renderers/RenderCodeMermaid';
import { RenderCodeSVG } from './code-renderers/RenderCodeSVG';
@@ -108,6 +110,7 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
const [showMermaid, setShowMermaid] = React.useState(true);
const [showPlantUML, setShowPlantUML] = React.useState(true);
const [showSVG, setShowSVG] = React.useState(true);
const [showChartJS, setShowChartJS] = React.useState(true);
const { showLineNumbers, showSoftWrap, setShowLineNumbers, setShowSoftWrap } = useUIPreferencesStore(useShallow(state => ({
showLineNumbers: state.renderCodeLineNumbers,
showSoftWrap: state.renderCodeSoftWrap,
@@ -141,10 +144,12 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
// heuristics for specialized rendering
const lcBlockTitle = blockTitle.trim().toLowerCase();
const isHTMLCode = heuristicIsBlockPureHTML(code);
const renderHTML = isHTMLCode && showHTML;
const isMermaidCode = blockTitle === 'mermaid' && !blockIsPartial;
const isMermaidCode = lcBlockTitle === 'mermaid' && !blockIsPartial;
const renderMermaid = isMermaidCode && showMermaid;
const isPlantUMLCode = heuristicIsCodePlantUML(code);
@@ -156,10 +161,11 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
const renderSVG = isSVGCode && showSVG;
const canScaleSVG = renderSVG && code.includes('viewBox="');
const renderSyntaxHighlight = !renderHTML && !renderMermaid && !renderPlantUML && !renderSVG;
const isChartJSCode = lcBlockTitle === 'chartjs' && !blockIsPartial;
const renderChartJS = isChartJSCode && showChartJS;
const cannotRenderLineNumbers = !renderSyntaxHighlight || showSoftWrap;
const renderSyntaxHighlight = !renderHTML && !renderMermaid && !renderPlantUML && !renderSVG && !renderChartJS;
const cannotRenderLineNumbers = !renderSyntaxHighlight || showSoftWrap || renderChartJS;
const renderLineNumbers = showLineNumbers && !cannotRenderLineNumbers;
@@ -242,7 +248,8 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
: renderMermaid ? <RenderCodeMermaid mermaidCode={code} fitScreen={fitScreen} />
: renderSVG ? <RenderCodeSVG svgCode={code} fitScreen={fitScreen} />
: (renderPlantUML && (plantUmlSvgData || plantUmlError)) ? <RenderCodePlantUML svgCode={plantUmlSvgData ?? null} error={plantUmlError} fitScreen={fitScreen} />
: <RenderCodeSyntax highlightedSyntaxAsHtml={highlightedCode} />}
: renderChartJS ? <RenderCodeChartJS chartJSCode={code} fitScreen={fitScreen} />
: <RenderCodeSyntax highlightedSyntaxAsHtml={highlightedCode} />}
</Box>
@@ -288,6 +295,19 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
</ButtonGroup>
)}
{/* Show ChartJS */}
{isChartJSCode && (
<OverlayButton
tooltip={noTooltips ? null : renderChartJS ? 'Show Code' : 'Render Chart'}
variant={renderChartJS ? 'solid' : 'outlined'}
color='primary'
smShadow
onClick={() => setShowChartJS(on => !on)}
>
<BarChartIcon />
</OverlayButton>
)}
{/* Group: Text Options */}
<ButtonGroup aria-label='Text and code options' sx={overlayGroupWithShadowSx}>
{/* Soft Wrap toggle */}
@@ -36,6 +36,10 @@ export function inferCodeLanguage(blockTitle: string, code: string): string | nu
// if we have a block title, use it to infer the language
if (blockTitle) {
// vnd.agi - we tell how to format these blocks, so we know what's the language inside
if (blockTitle.trim().toLowerCase() === 'chartjs')
return 'json'; // {{RenderChartJS}}
// single word: assume it's the syntax highlight language
if (!blockTitle.includes('.'))
return hFileExtensionsMap.hasOwnProperty(blockTitle) ? hFileExtensionsMap[blockTitle] : blockTitle;
@@ -0,0 +1,160 @@
import * as React from 'react';
import { create } from 'zustand';
import type { Chart as ChartType } from 'chart.js/auto';
import { useQuery } from '@tanstack/react-query';
import { Box, Typography } from '@mui/joy';
import { patchSvgString } from '~/modules/blocks/code/code-renderers/RenderCodeSVG';
import { diagramErrorSx, diagramSx } from '~/modules/blocks/code/code-renderers/RenderCodePlantUML';
// configuration
/**
* We are loading Chart.js from the CDN (and spending all the work to dynamically load it
* and strong type it), because the Chart.js dependencies (npm i chart.js) are too heavy
* and would slow down development for everyone.
*/
const CHARTJS_CDN_URL = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.js';
const CHARTJS_ERROR_PREFIX = '[Chart.js]';
// Store to react to loading of the library
interface ChartJSApiState {
chartJSAPI: any /* FIXME */ | null;
loadingError: string | null;
}
export const useChartJSStore = create<ChartJSApiState>(() => ({
chartJSAPI: null,
loadingError: null,
}));
// Dynamic loading of the Chart.js library
// extend the Window interface, to allow for the mermaid API to be found
// declare global {
// // noinspection JSUnusedGlobalSymbols
// interface Window {
// Chart: ;
// }
// }
let loadingStarted = false;
export function useChartJSLoader() {
const { chartJSAPI, loadingError } = useChartJSStore();
React.useEffect(() => {
if (!chartJSAPI && !loadingError && !loadingStarted) {
loadingStarted = true;
const script = document.createElement('script');
script.src = CHARTJS_CDN_URL;
script.async = true;
script.onload = () => {
if (window.Chart) {
useChartJSStore.setState({ chartJSAPI: window.Chart, loadingError: null });
} else {
useChartJSStore.setState({ chartJSAPI: null, loadingError: 'Chart.js failed to load.' });
}
};
script.onerror = () => {
useChartJSStore.setState({ chartJSAPI: null, loadingError: 'Failed to load Chart.js library.' });
};
document.head.appendChild(script);
}
}, [chartJSAPI, loadingError]);
return { chartJSAPI, loadingError };
}
interface RenderCodeChartJSProps {
chartJSCode: string;
fitScreen: boolean;
}
export function RenderCodeChartJS({ chartJSCode, fitScreen }: RenderCodeChartJSProps) {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
const chartInstanceRef = React.useRef<import('chart.js').Chart | null>(null);
const { chartJSAPI, loadingError } = useChartJSLoader();
const [chartConfig, setChartConfig] = React.useState<import('chart.js').ChartConfiguration | null>(null);
const [parseError, setParseError] = React.useState<string | null>(null);
const [renderError, setRenderError] = React.useState<string | null>(null);
// Parse the Chart.js configuration
React.useEffect(() => {
try {
const config = JSON.parse(chartJSCode) as import('chart.js').ChartConfiguration;
setChartConfig(config);
setParseError(null);
} catch (error: any) {
console.error('Chart.js configuration parse error:', error);
setChartConfig(null);
setParseError('Invalid Chart.js configuration: ' + (error.message || 'Unknown error.'));
}
}, [chartJSCode]);
// Render the chart
React.useEffect(() => {
if (chartJSAPI && chartConfig && canvasRef.current) {
try {
// Destroy previous chart instance
if (chartInstanceRef.current) {
chartInstanceRef.current.destroy();
}
// Create new chart instance
chartInstanceRef.current = new chartJSAPI(canvasRef.current, chartConfig);
setRenderError(null);
} catch (error: any) {
console.error('Chart.js rendering error:', error);
setRenderError('Error rendering chart: ' + (error.message || 'Unknown error.'));
}
}
return () => {
// Cleanup on unmount
if (chartInstanceRef.current) {
chartInstanceRef.current.destroy();
chartInstanceRef.current = null;
}
};
}, [chartJSAPI, chartConfig]);
// Handle error messages
if (loadingError) {
return (
<Typography color='danger' variant='plain' sx={{ mb: 2 }}>
{loadingError}
</Typography>
);
}
if (parseError) {
return (
<Typography color='danger' variant='plain' sx={{ mb: 2 }}>
{parseError}
</Typography>
);
}
if (renderError) {
return (
<Typography color='danger' variant='plain' sx={{ mb: 2 }}>
{renderError}
</Typography>
);
}
// Render the chart
return (
<Box sx={fitScreen ? { width: '100%', height: '100%' } : {}}>
<canvas ref={canvasRef} />
</Box>
);
}
+9
View File
@@ -83,6 +83,15 @@ export function bareBonesPromptMixer(_template: string, assistantLlmId: DLLMId |
// {{Prefer...}}
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:
\`\`\`chartjs
{
// Valid and complete ChartJS configuration JSON object (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.
`.trim());
mixed = mixed.replace('{{RenderMermaid}}', 'Mermaid rendering: Enabled for diagrams and pie charts and no other charts');
mixed = mixed.replace('{{RenderPlantUML}}', 'PlantUML rendering: Enabled');
mixed = mixed.replace('{{RenderHTML}}', `HTML in markdown rendering: Sleek HTML5 for ${Is.Desktop ? 'desktop' : 'mobile'} screens (self-contained with CSS/JS, leverage top libraries, external links OK)`);