feat(datasource): Support reasoning for ChatDashboard (#2401)

This commit is contained in:
Fangyin Cheng
2025-03-06 15:16:08 +08:00
committed by GitHub
parent 3bd75d8de2
commit bfd7fe8888
65 changed files with 391 additions and 216 deletions

View File

@@ -13,6 +13,76 @@ import MuiLoading from '../common/loading';
import Completion from './completion';
import Header from './header';
// Function to extract JSON from vis-thinking code blocks
const parseVisThinking = (content: any) => {
// Check if content is a string
if (typeof content !== 'string') {
return content;
}
// Check if this is a vis-thinking code block
if (content.startsWith('```vis-thinking') || content.includes('```vis-thinking')) {
// Find where the JSON part begins
// We're looking for the first occurrence of '{"' after the vis-thinking header
const jsonStartIndex = content.indexOf('{"');
if (jsonStartIndex !== -1) {
// Extract everything from the JSON start to the end
const jsonContent = content.substring(jsonStartIndex);
// Attempt to parse the JSON
try {
return JSON.parse(jsonContent);
} catch {
// If there's a parsing error, try to clean up the JSON string
// This might happen if there are backticks at the end
const cleanedContent = jsonContent.replace(/```$/g, '').trim();
try {
return JSON.parse(cleanedContent);
} catch (e2) {
console.error('Error parsing cleaned JSON:', e2);
return null;
}
}
}
}
// If it's not a vis-thinking block, try to parse it directly as JSON
try {
return typeof content === 'string' ? JSON.parse(content) : content;
} catch {
// If it's not valid JSON, return the original content
console.log('Not JSON format or vis-thinking format, returning original content');
return content;
}
};
// Function to extract the thinking part from vis-thinking code blocks while preserving tags
const formatToVisThinking = (content: any) => {
// Only process strings
if (typeof content !== 'string') {
return content;
}
// Check if this is a vis-thinking code block
if (content.startsWith('```vis-thinking') || content.includes('```vis-thinking')) {
// Find the start of the vis-thinking block
const blockStartIndex = content.indexOf('```vis-thinking');
const thinkingStartIndex = blockStartIndex + '```vis-thinking'.length;
// Find the end of the vis-thinking block
const thinkingEndIndex = content.indexOf('```', thinkingStartIndex);
if (thinkingEndIndex !== -1) {
// Extract the thinking content with the tags
return content.substring(blockStartIndex, thinkingEndIndex + 3);
}
}
// If it's not a vis-thinking block or can't extract thinking part, return the original content
return content;
};
const ChatContainer = () => {
const searchParams = useSearchParams();
const { scene, chatId, model, agent, setModel, history, setHistory } = useContext(ChatContext);
@@ -33,7 +103,17 @@ const ChatContainer = () => {
const contextTemp = list[list.length - 1]?.context;
if (contextTemp) {
try {
const contextObj = typeof contextTemp === 'string' ? JSON.parse(contextTemp) : contextTemp;
// First, parse the context to handle vis-thinking code blocks
const parsedContext = parseVisThinking(contextTemp);
// Then, handle the normal JSON processing
const contextObj =
typeof parsedContext === 'object'
? parsedContext
: typeof contextTemp === 'string'
? JSON.parse(contextTemp)
: contextTemp;
setChartsData(contextObj?.template_name === 'report' ? contextObj?.charts : undefined);
} catch (e) {
console.log(e);
@@ -127,7 +207,11 @@ const ChatContainer = () => {
'h-full lg:px-8': scene !== 'chat_dashboard',
})}
>
<Completion messages={history} onSubmit={handleChat} />
<Completion
messages={history}
onSubmit={handleChat}
onFormatContent={formatToVisThinking} // Pass the formatting function to Completion
/>
</div>
</div>
</>

View File

@@ -25,9 +25,10 @@ import MonacoEditor from './monaco-editor';
type Props = {
messages: IChatDialogueMessageSchema[];
onSubmit: (message: string, otherQueryBody?: Record<string, any>) => Promise<void>;
onFormatContent?: (content: any) => any; // Callback for extracting thinking part
};
const Completion = ({ messages, onSubmit }: Props) => {
const Completion = ({ messages, onSubmit, onFormatContent }: Props) => {
const { dbParam, currentDialogue, scene, model, refreshDialogList, chatId, agent, docId } = useContext(ChatContext);
const { t } = useTranslation();
const searchParams = useSearchParams();
@@ -78,18 +79,23 @@ const Completion = ({ messages, onSubmit }: Props) => {
}
};
const handleJson2Obj = (jsonStr: string) => {
try {
return JSON.parse(jsonStr);
} catch {
return jsonStr;
// Process message content - if onFormatContent is provided and this is a dashboard chat,
// we'll extract the thinking part from vis-thinking code blocks
const processMessageContent = (content: any) => {
if (isChartChat && onFormatContent && typeof content === 'string') {
return onFormatContent(content);
}
return content;
};
const [messageApi, contextHolder] = message.useMessage();
const onCopyContext = async (context: any) => {
const pureStr = context?.replace(/\trelations:.*/g, '');
// If we have a formatting function and this is a string, apply it before copying
const contentToCopy =
isChartChat && onFormatContent && typeof context === 'string' ? onFormatContent(context) : context;
const pureStr = contentToCopy?.replace(/\trelations:.*/g, '');
const result = copy(pureStr);
if (result) {
if (pureStr) {
@@ -124,14 +130,25 @@ const Completion = ({ messages, onSubmit }: Props) => {
let tempMessage: IChatDialogueMessageSchema[] = messages;
if (isChartChat) {
tempMessage = cloneDeep(messages).map(item => {
if (item?.role === 'view' && typeof item?.context === 'string') {
item.context = handleJson2Obj(item?.context);
if (item?.role === 'view') {
if (typeof item?.context === 'string') {
// Try to parse JSON first
try {
item.context = JSON.parse(item.context);
} catch {
// If JSON parsing fails and we have a formatting function,
// it might be a vis-thinking block, so process it
if (onFormatContent) {
item.context = processMessageContent(item.context);
}
}
}
}
return item;
});
}
setShowMessages(tempMessage.filter(item => ['view', 'human'].includes(item.role)));
}, [isChartChat, messages]);
}, [isChartChat, messages, onFormatContent]);
useEffect(() => {
apiInterceptors(getChatFeedBackSelect())