mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Aix: +Diagram
This commit is contained in:
@@ -10,13 +10,13 @@ import StopOutlinedIcon from '@mui/icons-material/StopOutlined';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
|
||||
import { AutoBlocksRenderer } from '~/modules/blocks/AutoBlocksRenderer';
|
||||
import { llmStreamingChatGenerate } from '~/modules/llms/llm.client';
|
||||
import { aixChatGenerateText_Simple } from '~/modules/aix/client/aix.client';
|
||||
|
||||
import { ConversationsManager } from '~/common/chat-overlay/ConversationsManager';
|
||||
import { GoodModal } from '~/common/components/modals/GoodModal';
|
||||
import { InlineError } from '~/common/components/InlineError';
|
||||
import { adjustContentScaling } from '~/common/app.theme';
|
||||
import { createDMessageTextContent, messageFragmentsReduceText } from '~/common/stores/chat/chat.message';
|
||||
import { useChatStore } from '~/common/stores/chat/store-chats';
|
||||
import { useFormRadio } from '~/common/components/forms/useFormRadio';
|
||||
import { useFormRadioLlmType } from '~/common/components/forms/useFormRadioLlmType';
|
||||
import { useIsMobile } from '~/common/components/useMatchMedia';
|
||||
@@ -29,10 +29,10 @@ import { bigDiagramPrompt, DiagramLanguage, diagramLanguages, DiagramType, diagr
|
||||
const DIAGRAM_ACTOR_PREFIX = 'diagram';
|
||||
|
||||
|
||||
// Used by the callers to setup the diagam session
|
||||
// Used by the callers to setup the diagram session
|
||||
export interface DiagramConfig {
|
||||
conversationId: string;
|
||||
messageId: string,
|
||||
messageId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@@ -69,9 +69,11 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
const [diagramLlm, llmComponent] = useFormRadioLlmType('Generator', 'chat');
|
||||
|
||||
// derived state
|
||||
const { conversationId, messageId, text: subject } = props.config;
|
||||
const { messageId, text: subject } = props.config;
|
||||
const diagramLlmId = diagramLlm?.id;
|
||||
|
||||
// conversation handler (to view history and eventually append the message)
|
||||
const cHandler = ConversationsManager.getHandler(props.config.conversationId);
|
||||
|
||||
/**
|
||||
* Core Diagram Generation function, with Streaming, custom prompt, etc.
|
||||
@@ -80,13 +82,8 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
if (abortController)
|
||||
return;
|
||||
|
||||
const conversation = useChatStore.getState().conversations.find(c => c.id === conversationId);
|
||||
if (!diagramType || !diagramLanguage || !diagramLlm || !conversation)
|
||||
return setErrorMessage('Invalid diagram Type, Language, Generator, or conversation.');
|
||||
|
||||
const systemMessage = conversation?.messages?.length ? conversation.messages[0] : null;
|
||||
if (systemMessage?.role !== 'system')
|
||||
return setErrorMessage('No System message in this conversation');
|
||||
if (!diagramType || !diagramLanguage || !diagramLlm)
|
||||
return setErrorMessage(`Invalid diagram Type, Language, or Model (${diagramLlm}).`);
|
||||
|
||||
setErrorMessage(null);
|
||||
|
||||
@@ -95,13 +92,28 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
|
||||
const stepAbortController = new AbortController();
|
||||
setAbortController(stepAbortController);
|
||||
// cHandler.setAbortController(stepAbortController);
|
||||
|
||||
const systemMessageText = messageFragmentsReduceText(systemMessage.fragments);
|
||||
const diagramPrompt = bigDiagramPrompt(diagramType, diagramLanguage, systemMessageText, subject, customInstruction);
|
||||
const history = cHandler.historyViewHead('diagrams-modal');
|
||||
const systemMessage = history.length > 0 ? history[0] : null;
|
||||
if (!systemMessage || systemMessage?.role !== 'system')
|
||||
return setErrorMessage('No System instruction in this conversation');
|
||||
|
||||
try {
|
||||
await llmStreamingChatGenerate(diagramLlm.id, diagramPrompt, 'ai-diagram', messageId, null, null, stepAbortController.signal,
|
||||
({ textSoFar }) => textSoFar && setDiagramCode(diagramCode = textSoFar),
|
||||
const { systemInstruction, messages } = bigDiagramPrompt(
|
||||
diagramType,
|
||||
diagramLanguage,
|
||||
messageFragmentsReduceText(systemMessage.fragments),
|
||||
subject,
|
||||
customInstruction,
|
||||
);
|
||||
await aixChatGenerateText_Simple(
|
||||
diagramLlm.id,
|
||||
systemInstruction,
|
||||
messages,
|
||||
'ai-diagram', messageId,
|
||||
{ abortSignal: stepAbortController.signal },
|
||||
(text) => !!text && setDiagramCode(diagramCode = text.trim()),
|
||||
);
|
||||
} catch (error: any) {
|
||||
setDiagramCode(null);
|
||||
@@ -111,18 +123,18 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
setAbortController(null);
|
||||
}
|
||||
|
||||
}, [abortController, conversationId, customInstruction, diagramLanguage, diagramLlm, diagramType, messageId, subject]);
|
||||
|
||||
}, [abortController, cHandler, customInstruction, diagramLanguage, diagramLlm, diagramType, messageId, subject]);
|
||||
|
||||
// [Effect] Auto-abort on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
// cHandler.setAbortController(null);
|
||||
setAbortController(null);
|
||||
}
|
||||
};
|
||||
}, [abortController]);
|
||||
}, [abortController, cHandler]);
|
||||
|
||||
|
||||
// custom instruction
|
||||
@@ -149,9 +161,9 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
// diagramMessage.purposeId = conversation.systemPurposeId;
|
||||
diagramMessage.generator = { mgt: 'named', name: DIAGRAM_ACTOR_PREFIX + (diagramLlmId ? `-${diagramLlmId}` : '') };
|
||||
|
||||
useChatStore.getState().appendMessage(conversationId, diagramMessage);
|
||||
cHandler.messageAppend(diagramMessage);
|
||||
props.onClose();
|
||||
}, [conversationId, diagramCode, diagramLlmId, props]);
|
||||
}, [cHandler, diagramCode, diagramLlmId, props]);
|
||||
|
||||
|
||||
// [effect] Auto-switch language to match diagram type
|
||||
@@ -258,7 +270,11 @@ export function DiagramsModal(props: { config: DiagramConfig, onClose: () => voi
|
||||
<Button
|
||||
variant={abortController ? 'soft' : 'solid'} color='primary'
|
||||
disabled={!diagramLlm}
|
||||
onClick={abortController ? () => abortController.abort() : handleGenerateNew}
|
||||
onClick={abortController ? () => {
|
||||
abortController.abort();
|
||||
// cHandler.setAbortController(null);
|
||||
setAbortController(null);
|
||||
} : handleGenerateNew}
|
||||
endDecorator={abortController ? <StopOutlinedIcon /> : diagramCode ? <ReplayIcon /> : <AccountTreeTwoToneIcon />}
|
||||
sx={{ minWidth: isMobile ? 160 : 220 }}
|
||||
>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { AixChatGenerate_TextMessages } from '~/modules/aix/client/aix.client.chatGenerateRequest';
|
||||
|
||||
import type { FormRadioOption } from '~/common/components/forms/FormRadioControl';
|
||||
import type { VChatMessageIn } from '~/modules/llms/llm.client';
|
||||
|
||||
|
||||
export type DiagramType = 'auto' | 'mind';
|
||||
export type DiagramLanguage = 'mermaid' | 'plantuml';
|
||||
|
||||
|
||||
// NOTE: keep these global, or it will trigger re-renders
|
||||
export const diagramTypes: FormRadioOption<DiagramType>[] = [
|
||||
{ label: 'Automatic', value: 'auto' },
|
||||
@@ -64,12 +66,20 @@ function mermaidDiagramPrompt(diagramType: DiagramType): { sys: string, usr: str
|
||||
const sysSuffixPM = 'The next three messages will outline: 1. your personality, 2. the data you\'ll work with, and 3. a clear restatement of the instructions.';
|
||||
const usrSuffixCoT = 'Please think step by step, then generate valid diagram code in a markdown block as instructed, and stop your response.';
|
||||
|
||||
export function bigDiagramPrompt(diagramType: DiagramType, diagramLanguage: DiagramLanguage, chatSystemPrompt: string, subject: string, customInstruction: string): VChatMessageIn[] {
|
||||
export function bigDiagramPrompt(
|
||||
diagramType: DiagramType,
|
||||
diagramLanguage: DiagramLanguage,
|
||||
chatSystemPrompt: string,
|
||||
subject: string,
|
||||
customInstruction: string,
|
||||
): { systemInstruction: string, messages: AixChatGenerate_TextMessages } {
|
||||
const { sys, usr } = diagramLanguage === 'mermaid' ? mermaidDiagramPrompt(diagramType) : plantumlDiagramPrompt(diagramType);
|
||||
return [
|
||||
{ role: 'system', content: sys + '\n' + sysSuffixPM },
|
||||
{ role: 'user', content: chatSystemPrompt },
|
||||
{ role: 'assistant', content: subject },
|
||||
{ role: 'user', content: (!customInstruction?.trim() ? usr : `${usr} Also consider the following instructions: ${customInstruction.trim()}`) + '\n' + usrSuffixCoT },
|
||||
];
|
||||
return {
|
||||
systemInstruction: sys + '\n' + sysSuffixPM,
|
||||
messages: [
|
||||
{ role: 'user', text: chatSystemPrompt },
|
||||
{ role: 'model', text: subject },
|
||||
{ role: 'user', text: (!customInstruction?.trim() ? usr : `${usr} Also consider the following instructions: ${customInstruction.trim()}`) + '\n' + usrSuffixCoT },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -75,8 +75,13 @@ export function AutoBlocksRenderer(props: {
|
||||
// state
|
||||
const { text, isTextCollapsed, forceTextExpanded, handleToggleExpansion } =
|
||||
useTextCollapser(props.text, fromUser);
|
||||
let autoBlocksStable =
|
||||
useAutoBlocksMemoSemiStable(text, props.renderAsCodeWithTitle, fromSystem, props.renderSanityTextDiffs);
|
||||
const autoBlocksStable = useAutoBlocksMemoSemiStable(
|
||||
text,
|
||||
props.renderAsCodeWithTitle,
|
||||
fromSystem,
|
||||
props.renderSanityTextDiffs,
|
||||
props.blocksProcessor === 'diagram',
|
||||
);
|
||||
|
||||
// handlers
|
||||
const { setText } = props;
|
||||
@@ -92,11 +97,6 @@ export function AutoBlocksRenderer(props: {
|
||||
}, [setText, text]);
|
||||
|
||||
|
||||
// apply specialDiagramMode filter if applicable
|
||||
if (props.blocksProcessor === 'diagram')
|
||||
autoBlocksStable = autoBlocksStable.filter(({ bkt }) => bkt === 'code-bk' || autoBlocksStable.length === 1);
|
||||
|
||||
|
||||
// Memo the styles, to minimize re-renders
|
||||
const scaledCodeSx = useScaledCodeSx(fromAssistant, props.contentScaling, props.codeRenderVariant || 'outlined');
|
||||
const scaledImageSx = useScaledImageSx(props.contentScaling);
|
||||
|
||||
@@ -61,7 +61,7 @@ function areBlocksEqualIdIgnored(block1: RenderBlockInputs[number] | undefined,
|
||||
* as part of the the running text, in which case the growing text will be
|
||||
* reassigned (when it's chopped to before the code block, in the next call)
|
||||
*/
|
||||
export function useAutoBlocksMemoSemiStable(text: string, forceCodeWithTitle: string | undefined, forceMarkdown: boolean, forceSanityTextDiffs: SanityTextDiff[] | undefined): RenderBlockInputs {
|
||||
export function useAutoBlocksMemoSemiStable(text: string, forceCodeWithTitle: string | undefined, forceMarkdown: boolean, forceSanityTextDiffs: SanityTextDiff[] | undefined, selectSingleCodeBlock: boolean): RenderBlockInputs {
|
||||
|
||||
// state - previous blocks, to stabilize objects
|
||||
const prevBlocksRef = React.useRef<RenderBlockInputs>([]);
|
||||
@@ -75,8 +75,11 @@ export function useAutoBlocksMemoSemiStable(text: string, forceCodeWithTitle: st
|
||||
newBlocks = [{ bkt: 'md-bk', content: text }];
|
||||
else if (forceSanityTextDiffs && forceSanityTextDiffs.length >= 1)
|
||||
newBlocks = [{ bkt: 'txt-diffs-bk', sanityTextDiffs: forceSanityTextDiffs }];
|
||||
else
|
||||
else {
|
||||
newBlocks = parseBlocksFromText(text);
|
||||
if (selectSingleCodeBlock && newBlocks.length > 1)
|
||||
newBlocks = newBlocks.filter(({ bkt }) => bkt === 'code-bk');
|
||||
}
|
||||
|
||||
const recycledBlocks: RenderBlockInputs = newBlocks.map((newBlock, index) => {
|
||||
const prevBlock = prevBlocksRef.current[index] ?? undefined;
|
||||
|
||||
@@ -158,7 +158,7 @@ function RenderCodeImpl(props: RenderCodeBaseProps & {
|
||||
const isMermaidCode = lcBlockTitle === 'mermaid' && !blockIsPartial;
|
||||
const renderMermaid = isMermaidCode && showMermaid;
|
||||
|
||||
const isPlantUMLCode = heuristicIsCodePlantUML(code);
|
||||
const isPlantUMLCode = heuristicIsCodePlantUML(code.trim());
|
||||
let renderPlantUML = isPlantUMLCode && showPlantUML;
|
||||
const { data: plantUmlSvgData, error: plantUmlError } = usePlantUmlSvg(renderPlantUML, code);
|
||||
renderPlantUML = renderPlantUML && (!!plantUmlSvgData || !!plantUmlError);
|
||||
|
||||
Reference in New Issue
Block a user