Skip to content

Commit a5fc2ec

Browse files
authored
Analyst Mode UI changes (#252)
* simple visual changes * add good badge and spacing styles * fix undo buttons * add more context checks * modify tecxt
1 parent 3490de0 commit a5fc2ec

File tree

6 files changed

+184
-99
lines changed

6 files changed

+184
-99
lines changed

apps/src/metabase/appController.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,17 +352,17 @@ export class MetabaseController extends AppController<MetabaseAppState> {
352352

353353
@Action({
354354
labelRunning: "Executes the SQL query with parameters",
355-
labelDone: "Executed query with parameters",
356-
labelTask: "Executed SQL query with parameters",
355+
labelDone: "Executed query",
356+
labelTask: "Executed SQL query",
357357
description: "Executes the SQL query in the Metabase SQL editor with support for template tags and parameters.",
358358
renderBody: ({ sql, explanation }: { sql: string, explanation: string }, appState: MetabaseAppStateSQLEditor) => {
359359
const sqlQuery = appState?.sqlQuery
360360
return {text: explanation, code: sql, oldCode: sqlQuery, language: "sql"}
361361
}
362362
})
363363
async ExecuteQuery({ sql, _ctes = [], explanation = "", template_tags={}, parameters=[] }: { sql: string, _ctes?: CTE[], explanation?: string, template_tags?: object, parameters?: any[] }) {
364-
console.log('Template tags are', template_tags)
365-
console.log('Parameters are', parameters)
364+
// console.log('Template tags are', template_tags)
365+
// console.log('Parameters are', parameters)
366366
// Try parsing template_tags and parameters if they are strings
367367
try {
368368
if (typeof template_tags === 'string') {

web/src/components/common/ActionStack.tsx

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { parseArguments } from '../../planner/plannerActions';
2020
import { CodeBlock } from './CodeBlock';
2121
import { ActionRenderInfo } from '../../state/chat/types';
2222
import { Markdown } from './Markdown';
23+
import {processModelToUIText} from '../../helpers/utils';
2324

2425
// Todo: Vivek: Hardcoding here, need to fix this later
2526
// This is a list of actions that are undo/redoable
@@ -52,6 +53,9 @@ export const ActionStack: React.FC<{status: string, actions: Array<ActionStatusV
5253
const currentTool = useSelector((state: RootState) => state.settings.iframeInfo.tool)
5354
const controller = getApp().actionController
5455
const pageType = useAppStore((state) => state.toolContext.pageType) || '';
56+
const url = useAppStore((state) => state.toolContext.url) || '';
57+
const origin = url ? new URL(url).origin : '';
58+
5559
const getActionLabels = (action: string, attr: string) => {
5660
if (controller) {
5761
const metadata = Reflect.getMetadata('actionMetadata', controller, action);
@@ -72,49 +76,50 @@ export const ActionStack: React.FC<{status: string, actions: Array<ActionStatusV
7276
title = [...new Set(titles)].join(', ')
7377

7478
}
75-
let preExpanderText = actions.map(action => {
79+
const preExpanderTextArr = actions.map(action => {
7680
const { text } = action.renderInfo || {}
77-
return text || ''
78-
}).filter(text => text !== '').join(', ')
81+
return processModelToUIText(text || '', origin)
82+
}).filter(text => text !== '')
83+
84+
// const preExpanderText = preExpanderTextArr.length > 1
85+
// ? preExpanderTextArr.map((text, i) => {
86+
// return `\n\n **Query ${i+1}**: ${text} \n\n ---`;
87+
// }).join('\n\n')
88+
// : preExpanderTextArr.join('\n\n');
89+
7990

8091

81-
const UndoRedo: React.FC<{fn: string, sql: string, type: 'undo' | 'redo'}> = ({fn, sql, type}) => {
82-
const urHandler = (event: React.MouseEvent, fn: string, sql: string) => {
83-
event.preventDefault();
84-
event.stopPropagation();
85-
executeAction({
86-
index: -1,
87-
function: fn,
88-
args: {sql: sql},
89-
});
92+
const UndoRedo: React.FC<{fn: string, sql: string, type: 'undo' | 'redo'}> = ({fn, sql, type}) => {
93+
const urHandler = (event: React.MouseEvent, fn: string, sql: string) => {
94+
event.preventDefault();
95+
event.stopPropagation();
96+
executeAction({
97+
index: -1,
98+
function: fn,
99+
args: {sql: sql},
100+
});
101+
};
102+
103+
return <Button
104+
size="xs"
105+
w={"100%"}
106+
leftIcon={ type === 'undo' ? <BiUndo /> : <BiRedo /> }
107+
variant={'solid'}
108+
colorScheme="minusxGreen"
109+
onClick={(event) => urHandler(event, fn, sql)}>
110+
{type === 'undo' ? 'Undo' : 'Redo'}
111+
</Button>
90112
};
91-
92-
return <Button
93-
size="xs"
94-
leftIcon={ type === 'undo' ? <BiUndo /> : <BiRedo /> }
95-
variant={'solid'}
96-
colorScheme="minusxGreen"
97-
onClick={(event) => urHandler(event, fn, sql)}>
98-
{type === 'undo' ? 'Undo' : 'Redo'}
99-
</Button>
100-
};
101113

102-
const PreExpanderUndo: React.FC = () => {
103-
return (
104-
<>
105-
{actions.map(action => {
114+
const undoRedoArr = actions.map(action => {
106115
const { code, oldCode } = action.renderInfo || {}
107116
return UNDO_REDO_ACTIONS.includes(action.function.name) && (
108-
<HStack>
109-
{oldCode && <UndoRedo fn={action.function.name} sql={oldCode} type={'undo'}/> }
110-
{code && <UndoRedo fn={action.function.name} sql={code} type={'redo'}/> }
117+
<HStack w={"100%"} justify={"center"} mb={2}>
118+
{oldCode ? <UndoRedo fn={action.function.name} sql={oldCode} type={'undo'}/>:<UndoRedo fn={action.function.name} sql={''} type={'undo'}/>}
119+
{code ? <UndoRedo fn={action.function.name} sql={code} type={'redo'}/> : <UndoRedo fn={action.function.name} sql={''} type={'redo'}/> }
111120
</HStack>
112121
)
113-
})}
114-
</>
115-
);
116-
};
117-
122+
})
118123

119124
const toggleExpand = () => {
120125
setIsExpanded(!isExpanded)
@@ -151,14 +156,18 @@ const PreExpanderUndo: React.FC = () => {
151156
p={0}
152157
>
153158
<VStack alignItems={"start"} flex={1} spacing={0}>
154-
{preExpanderText !== '' &&
159+
<VStack>
160+
{preExpanderTextArr.length > 0 &&
155161
// <Text marginBottom={2} borderBottomWidth={1} borderBottomColor={'minusxGreen.800'} style={{ hyphens: 'auto' }} p={2} w={"100%"}>{"Thinking..."}<br/>{preExpanderText}</Text>
156-
<Box aria-label="thinking-content" borderBottomWidth={1} mb={1} borderBottomColor={'minusxGreen.800'}>
157-
<Markdown content={`Thinking...
158-
${preExpanderText}`}></Markdown>
159-
</Box>
160-
161-
}
162+
preExpanderTextArr.map((text, i) => {
163+
return (
164+
<Box aria-label="thinking-content" borderBottomWidth={1} mb={1} borderBottomColor={'minusxGreen.800'} w={"100%"}>
165+
<Markdown content={text}></Markdown>
166+
{pageType && pageType == 'sql' && undoRedoArr[i]}
167+
</Box>
168+
)
169+
})}
170+
</VStack>
162171
<HStack
163172
aria-label="thinking-header"
164173
paddingBottom={actions.length && isExpanded ? 1 : 0}
@@ -178,7 +187,6 @@ const PreExpanderUndo: React.FC = () => {
178187
{ status != 'FINISHED' ? <Spinner size="xs" speed={'0.75s'} color="minusxBW.100" mx={3} /> : null }
179188
</HStack>
180189
{/* { isExpanded ? <Text fontSize={"12px"} flexDirection={"row"} display={"flex"} justifyContent={"center"} alignItems={"center"}><MdOutlineTimer/>{latency}{"s"}</Text> : null } */}
181-
{pageType && pageType == 'sql' && <PreExpanderUndo />}
182190
</HStack>
183191
</VStack>
184192
</HStack>

web/src/components/common/Markdown.tsx

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import MarkdownComponent from 'react-markdown'
33
import remarkGfm from 'remark-gfm'
44
import './ChatContent.css'
55
import { TransformWrapper, TransformComponent } from "react-zoom-pan-pinch";
6-
import { Image, Box, Button, Collapse } from "@chakra-ui/react"
6+
import { Image, Box, Collapse, Tag, TagLabel, TagLeftIcon, Button } from "@chakra-ui/react"
77
import { useSelector } from 'react-redux'
88
import { RootState } from '../../state/store'
99
import { renderString } from '../../helpers/templatize'
@@ -15,54 +15,34 @@ import type { MetabaseModel } from 'apps/types'
1515
import { Badge } from "@chakra-ui/react";
1616
import { CodeBlock } from './CodeBlock';
1717
import { BiChevronDown, BiChevronRight } from 'react-icons/bi';
18+
import { BsBarChartFill } from "react-icons/bs";
1819

1920

2021
function LinkRenderer(props: any) {
21-
return (
22+
if (props.children.toString().includes('Card ID')) {
23+
return (
24+
<a href={props.href} target="_blank" rel="minusxapp">
25+
{/* <Button leftIcon={<BsBarChartFill />} size={"xs"} colorScheme={"minusxGreen"}>{props.children}</Button> */}
26+
<Tag size='sm' colorScheme='minusxGreen' variant='solid'>
27+
<TagLeftIcon as={BsBarChartFill} />
28+
<TagLabel>{props.children}</TagLabel>
29+
</Tag>
30+
</a>
31+
)
32+
}
33+
return (
2234
<a href={props.href} target="_blank" rel="minusxapp" style={{color: '#5f27cd'}}>
2335
<u>{props.children}</u>
2436
</a>
2537
);
2638
}
2739

28-
const processRogueParagraphs = (text: string | string[]) => {
29-
const badgeTypes = [
30-
{ tag: '[badge_mx]Sources', label: 'Sources' },
31-
{ tag: '[badge_mx]Logic', label: 'Logic' },
32-
{ tag: '[badge_mx]Assumptions', label: 'Assumptions' }
33-
];
34-
35-
let processedText = text;
36-
let resultElements: React.ReactNode[] = [];
37-
38-
for (const badge of badgeTypes) {
39-
if (processedText.includes(badge.tag)) {
40-
if (typeof processedText === 'string') {
41-
const parts = processedText.split(badge.tag);
42-
if (parts[0]) resultElements.push(parts[0]);
43-
// Add a line break before each badge
44-
resultElements.push(<br key={`br-${badge.label}`}></br>);
45-
resultElements.push(
46-
<Badge key={`${badge.label.toLowerCase()}`} bg="minusxGreen.600" color="white" mx={1}>
47-
{badge.label}
48-
</Badge>
49-
);
50-
processedText = parts[1];
51-
}
52-
}
53-
}
54-
55-
// Add any remaining text
56-
if (processedText) resultElements.push(processedText);
57-
58-
return resultElements;
59-
};
6040

6141
function ModifiedParagraph(props: any) {
6242

6343
return (
64-
<p style={{margin: '3px', wordBreak: 'break-word', overflowWrap: 'break-word', wordWrap: 'break-word', whiteSpace: 'normal'}}>
65-
{props.children?.toString().includes('[badge_mx]') ? processRogueParagraphs(props.children) : props.children}
44+
<p style={{margin: '3px', wordBreak: 'break-word', overflowWrap: 'break-word', wordWrap: 'break-word', whiteSpace: 'normal', hyphens: 'auto'}}>
45+
{props.children}
6646
</p>
6747
)
6848
}
@@ -103,7 +83,7 @@ function ModifiedCode(props: any) {
10383
return <Badge color={"minusxGreen.600"}>{text.replace('[badge]', '')}</Badge>;
10484
}
10585
if (text.startsWith('[badge_mx]')) {
106-
return <><br></br><Badge bg={"minusxGreen.600"} color={"white"}>{text.replace('[badge_mx]', '')}</Badge></>;
86+
return <><br></br><Badge borderLeftColor={"minusxGreen.600"} borderLeft={"2px solid"} color={"minusxGreen.600"} fontSize={"sm"} mt={2}>{text.replace('[badge_mx]', '')}</Badge><br></br></>;
10787
}
10888
}
10989

web/src/components/common/TaskUI.tsx

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ const TaskUI = forwardRef<HTMLTextAreaElement>((_props, ref) => {
106106

107107
const credits = useSelector((state: RootState) => state.billing.credits)
108108
const infoLoaded = useSelector((state: RootState) => state.billing.infoLoaded)
109+
const analystMode = useSelector((state: RootState) => state.settings.analystMode)
110+
109111
const creditsExhausted = () => (credits <= 0 && infoLoaded)
110112
const creditsLow = () => (credits <= LOW_CREDITS_THRESHOLD && infoLoaded)
111113

@@ -556,19 +558,30 @@ const TaskUI = forwardRef<HTMLTextAreaElement>((_props, ref) => {
556558
gap={0}
557559
alignItems={"center"}
558560
>
559-
<Tooltip hasArrow placement='top' borderRadius={5} width={150}label="Entities can be Base Tables, Metabase Models or MinusX Catalog Entities"><Text mb={0} pb={0} fontSize={"xs"} fontWeight={"bold"} textTransform={"uppercase"} color={"minusxGreen.600"}>{entitiesInContext} {entitiesInContext != 1 ? 'entities' : 'entity' } in context</Text></Tooltip>
560-
<Button
561-
size="xs"
562-
colorScheme="minusxGreen"
563-
variant="outline"
564-
fontSize="xs"
565-
fontWeight="medium"
566-
py={0}
567-
px={3}
568-
onClick={()=>openDevtoolTab("Context")}
569-
>
570-
{selectedCatalog.slice(0, 15)}{selectedCatalog.length > 12 ? '...' : ''}
571-
</Button>
561+
{
562+
analystMode ?
563+
<Tooltip hasArrow placement='top' borderRadius={5} width={150}label="Context contains Base Tables, Metabase Models, Cards and Dashboards. MinusX figures out the rest.">
564+
<Text mb={0} pb={0} fontSize={"xs"} fontWeight={"bold"} textTransform={"uppercase"} color={"minusxGreen.600"}>Everything in context</Text>
565+
</Tooltip>
566+
:
567+
<>
568+
<Tooltip hasArrow placement='top' borderRadius={5} width={150}label="Entities can be Base Tables, Metabase Models or MinusX Catalog Entities">
569+
<Text mb={0} pb={0} fontSize={"xs"} fontWeight={"bold"} textTransform={"uppercase"} color={"minusxGreen.600"}>{entitiesInContext} {entitiesInContext != 1 ? 'entities' : 'entity' } in context</Text>
570+
</Tooltip>
571+
<Button
572+
size="xs"
573+
colorScheme="minusxGreen"
574+
variant="outline"
575+
fontSize="xs"
576+
fontWeight="medium"
577+
py={0}
578+
px={3}
579+
onClick={()=>openDevtoolTab("Context")}
580+
>
581+
{selectedCatalog.slice(0, 15)}{selectedCatalog.length > 12 ? '...' : ''}
582+
</Button>
583+
</>
584+
}
572585
</HStack>
573586
}
574587

web/src/components/devtools/Context.tsx

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { getParsedIframeInfo } from "../../helpers/origin"
1313
import { isEmpty, set } from 'lodash';
1414
import { MetabaseContext } from 'apps/types';
1515
import { BiBook, BiExpand } from "react-icons/bi";
16-
import { BsMagic } from "react-icons/bs";
16+
import { BsMagic, BsRocketTakeoffFill } from "react-icons/bs";
1717
import { MetabaseAppStateDashboard } from "../../../../apps/src/metabase/helpers/DOMToState";
1818
import { getLLMResponse } from "../../app/api";
1919
import _ from 'lodash';
@@ -30,6 +30,7 @@ const CatalogDisplay = ({isInModal, modalOpen}: {isInModal: boolean, modalOpen:
3030
const currentUserId = useSelector((state: RootState) => state.auth.profile_id)
3131
const toolContext: MetabaseContext = useAppStore((state) => state.toolContext)
3232
const viewAllCatalogs = useSelector((state: RootState) => state.settings.viewAllCatalogs)
33+
const analystMode = useSelector((state: RootState) => state.settings.analystMode)
3334
const origin = getParsedIframeInfo().origin
3435
// Enable to limit catalog visibility
3536
// const visibleCatalogs = viewAllCatalogs ? availableCatalogs : availableCatalogs.filter((catalog: ContextCatalog) => !catalog.origin || catalog.origin === origin)
@@ -92,8 +93,7 @@ const CatalogDisplay = ({isInModal, modalOpen}: {isInModal: boolean, modalOpen:
9293
setIsCreatingDashboardToCatalog(false)
9394
})
9495
}
95-
96-
return (
96+
const catalogContent = (
9797
<>
9898
<Box display="flex" alignItems="center" justifyContent="space-between">
9999
<Text fontSize="lg" fontWeight="bold">Available Catalogs</Text>
@@ -162,6 +162,69 @@ const CatalogDisplay = ({isInModal, modalOpen}: {isInModal: boolean, modalOpen:
162162
)}
163163
</>
164164
)
165+
166+
return (
167+
<Box position="relative">
168+
{catalogContent}
169+
{analystMode && (
170+
<Box
171+
position="absolute"
172+
top={0}
173+
left={0}
174+
width="100%"
175+
height="100%"
176+
bg="rgba(255, 255, 255, 0.5)"
177+
backdropFilter="blur(4px)"
178+
zIndex={1000}
179+
display="flex"
180+
alignItems="center"
181+
justifyContent="center"
182+
>
183+
<Box
184+
bg="white"
185+
borderRadius="xl"
186+
boxShadow="2xl"
187+
p={8}
188+
mx={6}
189+
maxWidth="400px"
190+
border="1px solid"
191+
borderColor="gray.200"
192+
transform="scale(1)"
193+
transition="all 0.2s ease-in-out"
194+
_hover={{
195+
transform: 'scale(1.02)',
196+
boxShadow: '3xl'
197+
}}
198+
display="flex"
199+
flexDirection="column"
200+
alignItems="center"
201+
>
202+
<Box
203+
width="60px"
204+
height="60px"
205+
borderRadius="full"
206+
bg="minusxGreen.100"
207+
display="flex"
208+
alignItems="center"
209+
justifyContent="center"
210+
mb={4}
211+
>
212+
<BsRocketTakeoffFill size={24} color="white" />
213+
</Box>
214+
<Box
215+
fontSize="md"
216+
fontWeight="medium"
217+
color="gray.700"
218+
textAlign="center"
219+
lineHeight="1.6"
220+
>
221+
In Analyst Mode, MinusX automatically figures out what matters with smart search and context awareness — no manual setup required! Like Claude Code, it just works.
222+
</Box>
223+
</Box>
224+
</Box>
225+
)}
226+
</Box>
227+
)
165228
}
166229

167230

0 commit comments

Comments
 (0)