mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-11 14:10:15 -07:00
Drop the charts module as-is, will fix later.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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)`);
|
||||
|
||||
Reference in New Issue
Block a user