From 56bb6a59af7f3effc85996f941e657997ef80c08 Mon Sep 17 00:00:00 2001 From: Jim Nielsen Date: Thu, 4 May 2023 20:28:27 -0600 Subject: [PATCH] feat: AI ui/ux enhancements (#466) --- README.md | 12 +- src/ui/components/ConditionalWrapper.tsx | 13 ++ src/ui/components/TooltipHint.tsx | 2 +- src/ui/menus/CodeEditor/AICodeBlockParser.tsx | 1 + src/ui/menus/CodeEditor/AITab.css | 8 + src/ui/menus/CodeEditor/AITab.tsx | 174 +++++++++++------- 6 files changed, 135 insertions(+), 75 deletions(-) create mode 100644 src/ui/components/ConditionalWrapper.tsx create mode 100644 src/ui/menus/CodeEditor/AITab.css diff --git a/README.md b/README.md index bd3766a9f..53b16ceb2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Twitter Follow](https://img.shields.io/twitter/follow/QuadraticHQ) ![quadraticlogo4 1](https://user-images.githubusercontent.com/3479421/162037216-2fea1620-2310-4cfa-96fb-31299195e3a9.png) -## ![quardatic icon small](https://user-images.githubusercontent.com/3479421/162039117-02f85f2c-e382-4ed8-ac39-64efab17a144.svg) **_The Data Science Spreadsheet_** +## ![quardatic icon small](https://user-images.githubusercontent.com/3479421/162039117-02f85f2c-e382-4ed8-ac39-64efab17a144.svg) **_The data science spreadsheet_** Infinite data grid with Python, JavaScript, and SQL built-in. Data Connectors to pull in your data. @@ -10,7 +10,7 @@ Take your data and do something useful with it as quickly and easily as possible Screenshot 2023-02-24 at 2 57 36 PM -## Online Demo +## Online demo We have a hosted version of the `main` branch available online. @@ -39,9 +39,9 @@ Quadratic has no environment to configure. The grid runs entirely in the browser - Quickly mix data from different sources - Explore your data for new insights -# Getting Started +# Getting started -### Run Quadratic Locally +### Run Quadratic locally 1. Install npm, [rustup](https://www.rust-lang.org/tools/install), and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) 2. Run `rustup target add wasm32-unknown-unknown` @@ -52,7 +52,7 @@ Run Web `npm start` Run Electron `npm run dev` -# Development Progress and Roadmap +# Development progress and roadmap _Quadratic is in ALPHA. For now, we do not recommend relying on Quadratic._ @@ -79,6 +79,6 @@ There are more example files are in the application file menu. File > Open sampl You can download them and then open them in Quadratic via File > Open Grid -## Quadratic is Hiring +## Quadratic is hiring Check out our open roles -> [careers.quadratichq.com](https://careers.quadratichq.com) diff --git a/src/ui/components/ConditionalWrapper.tsx b/src/ui/components/ConditionalWrapper.tsx new file mode 100644 index 000000000..ad4bac99a --- /dev/null +++ b/src/ui/components/ConditionalWrapper.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +interface ConditionalWrapperProps { + children: React.ReactNode; + condition: boolean; + Wrapper: React.FC<{ children: React.ReactNode }>; +} + +const ConditionalWrapper = ({ condition, Wrapper, children }: ConditionalWrapperProps) => { + return condition ? {children} : <>{children}; +}; + +export default ConditionalWrapper; diff --git a/src/ui/components/TooltipHint.tsx b/src/ui/components/TooltipHint.tsx index cf1d6da98..6cfa47b79 100644 --- a/src/ui/components/TooltipHint.tsx +++ b/src/ui/components/TooltipHint.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@mui/material'; interface TooltipHintProps { title: string; shortcut?: string; - children: JSX.Element; + children: React.ReactElement; // Anything else for you want to pass [x: string]: any; } diff --git a/src/ui/menus/CodeEditor/AICodeBlockParser.tsx b/src/ui/menus/CodeEditor/AICodeBlockParser.tsx index f846078de..2efa894e4 100644 --- a/src/ui/menus/CodeEditor/AICodeBlockParser.tsx +++ b/src/ui/menus/CodeEditor/AICodeBlockParser.tsx @@ -14,6 +14,7 @@ function parseCodeBlocks(input: string): Array { } blocks.push(
{ const [prompt, setPrompt] = useState(''); const [loading, setLoading] = useState(false); const [messages, setMessages] = useState([]); + const controller = useRef(); const { user } = useAuth0(); + const abortPrompt = () => { + controller.current?.abort(); + setLoading(false); + }; + const submitPrompt = async () => { if (loading) return; + controller.current = new AbortController(); setLoading(true); const token = await apiClientSingleton.getAuth(); const updatedMessages = [...messages, { role: 'user', content: prompt }] as Message[]; @@ -60,15 +70,17 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { }; setMessages(updatedMessages); setPrompt(''); - try { - await fetch(`${apiClientSingleton.getAPIURL()}/ai/chat/stream`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify(request_body), - }).then((response) => { + + await fetch(`${apiClientSingleton.getAPIURL()}/ai/chat/stream`, { + method: 'POST', + signal: controller.current.signal, + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(request_body), + }) + .then((response) => { if (response.status !== 200) { if (response.status === 429) { setMessages((old) => [ @@ -129,10 +141,16 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { } return reader.read().then(processResult); }); + }) + .catch((err) => { + // not sure what would cause this to happen + if (err.name !== 'AbortError') { + console.log(err); + return; + } }); - } catch (err: any) { - // not sure what would cause this to happen - } + // eslint-disable-next-line no-unreachable + setLoading(false); }; @@ -148,7 +166,7 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { right: '1rem', padding: '1rem 0 .5rem 1rem', background: 'linear-gradient(0deg, rgba(255,255,255,1) 85%, rgba(255,255,255,0) 100%)', - zIndex: 1000000, + zIndex: 100, }} > @@ -159,7 +177,7 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { setPrompt(event.target.value); }} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' && prompt.length > 0) { submitPrompt(); } }} @@ -167,9 +185,28 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { endAdornment={ {loading && } - - - + {loading ? ( + + + + + + ) : ( + {children as React.ReactElement}} + > + + + + + )} } size="small" @@ -203,7 +240,7 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => { data-gramm_editor="false" data-enable-grammarly="false" > - {display_message.length === 0 && ( + {display_message.length === 0 ? (
@@ -214,55 +251,56 @@ export const AITab = ({ evalResult, editorMode, editorContent }: Props) => {
+ ) : ( +
+ {display_message.map((message, index) => ( +
+ {message.role === 'user' ? ( + + ) : ( + + + + )} + {CodeBlockParser({ input: message.content })} +
+ ))} +
+
)} - {display_message.map((message, index) => { - return ( -
- {message.role === 'user' ? ( - - ) : ( - - - - )} - {CodeBlockParser({ input: message.content })} -
- ); - })} - {}
);