diff --git a/frontend/desktop/package.json.md5 b/frontend/desktop/package.json.md5 index 562c739e..48fc6112 100755 --- a/frontend/desktop/package.json.md5 +++ b/frontend/desktop/package.json.md5 @@ -1 +1 @@ -a24ece3a3d86e13594977247e3352385 \ No newline at end of file +371e648509f99cc686955a6284995fb5 \ No newline at end of file diff --git a/frontend/desktop/src/components/dbfragments/console.module.scss b/frontend/desktop/src/components/dbfragments/console.module.scss index 42e7af43..5296f3bf 100644 --- a/frontend/desktop/src/components/dbfragments/console.module.scss +++ b/frontend/desktop/src/components/dbfragments/console.module.scss @@ -22,4 +22,8 @@ left: 0; } + .consoleend { + height: 100px; + } + } \ No newline at end of file diff --git a/frontend/desktop/src/components/dbfragments/console.tsx b/frontend/desktop/src/components/dbfragments/console.tsx index cce862f8..539da66d 100644 --- a/frontend/desktop/src/components/dbfragments/console.tsx +++ b/frontend/desktop/src/components/dbfragments/console.tsx @@ -15,20 +15,20 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => { const currentTab: Tab = useContext(TabContext)! - const consoleEndRef = useRef(null) + const consoleRef = useRef(null) + const consoleEndRef = useRef(null) const dbConnection = useAppSelector(selectDBConnection) const output = useAppSelector(selectBlocks) const [input, setInput] = useState("") const [nfocus, setFocus] = useState(0) - const commands = output.filter( e => e.cmd === true) - const [pointer, setPointer] = useState(commands.length-1) + const history = output.filter(e => e.cmd).filter(e => e.text !== "").map(e => e.text) useEffect(() => { dispatch(initConsole(dbConnection!.id)) }, [dbConnection]) useEffect(() => { - consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + scrollToBottom("smooth") }, [output]) const confirmInput = () => { @@ -37,12 +37,18 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => { } const focus = (e: any) => { - if (e.target.id === "console") { + if (consoleRef.current?.contains(e.target)) { setFocus(Math.random()) } } - return
+ const scrollToBottom = (behavior: ScrollBehavior) => { + const mainContentDiv = consoleRef.current?.parentNode as HTMLDivElement + if (mainContentDiv.scrollTop !== consoleEndRef.current?.offsetTop) + mainContentDiv.scrollTo({ top: consoleEndRef.current?.offsetTop, behavior }) + } + + return
{ {output.map((block, idx) => { return })} - - + +
} @@ -66,8 +72,10 @@ const OutputBlock = ({ block }: any) => { const PromptInputWithRef = (props: any) => { const defaultValue = useRef("") const inputRef = useRef(null) + const [pointer, setPointer] = useState(-1) useEffect(() => { if (props.isActive) { + props.scrollToBottom("instant") inputRef.current?.focus() } }, [props.isActive, props.nfocus]) @@ -78,8 +86,8 @@ const PromptInputWithRef = (props: any) => { } } - const setInputRef = ( cmd : string) => { - if(inputRef.current !== null){ + const setInputRef = (cmd: string) => { + if (inputRef.current !== null) { inputRef.current.textContent = cmd; } } @@ -89,14 +97,30 @@ const PromptInputWithRef = (props: any) => { if (inputRef.current) { inputRef.current.innerText = "" } + setPointer(-1) + } + const updateInputFromPointer = (newPointer: number) => { + let text = props.history.at(props.history.length - 1 - newPointer) + if (!text) { + text = "" + } + setInputRef(text) } - if ( event.key.toLocaleLowerCase() === 'arrowup') { - props.setPointer( () => ((props.pointer + props.commands.length -1 ) % props.commands.length)) - setInputRef(props.commands.at(props.pointer)?.text) + if (event.key.toLocaleLowerCase() === 'arrowup') { + if (pointer !== props.history.length - 1) { + setPointer(() => (pointer + 1)) + updateInputFromPointer(pointer + 1) + } } - if ( event.key.toLocaleLowerCase() === 'arrowdown'){ - props.setPointer( () => ((props.pointer + 1 ) % props.commands.length)) - setInputRef(props.commands.at(props.pointer)?.text) + if (event.key.toLocaleLowerCase() === 'arrowdown') { + let newPointer + if (pointer < 0) { + newPointer = -1 + } else { + newPointer = pointer - 1 + } + setPointer(newPointer) + updateInputFromPointer(newPointer) } } diff --git a/frontend/desktop/src/redux/consoleSlice.ts b/frontend/desktop/src/redux/consoleSlice.ts index fbac292e..d2476445 100644 --- a/frontend/desktop/src/redux/consoleSlice.ts +++ b/frontend/desktop/src/redux/consoleSlice.ts @@ -22,6 +22,9 @@ export const runConsoleCmd = createAsyncThunk( async (payload: { dbConnId: string, cmdString: string }, { rejectWithValue, getState }: any) => { const dbConnectionId = payload.dbConnId const cmdString = payload.cmdString + if (cmdString === "") { + return rejectWithValue("empty command") + } const result = await eventService.runConsoleCommand(dbConnectionId, cmdString) if (result.success) { return { diff --git a/frontend/server/src/components/dbfragments/console.module.scss b/frontend/server/src/components/dbfragments/console.module.scss index 42e7af43..5296f3bf 100644 --- a/frontend/server/src/components/dbfragments/console.module.scss +++ b/frontend/server/src/components/dbfragments/console.module.scss @@ -22,4 +22,8 @@ left: 0; } + .consoleend { + height: 100px; + } + } \ No newline at end of file diff --git a/frontend/server/src/components/dbfragments/console.tsx b/frontend/server/src/components/dbfragments/console.tsx index 197d3062..81e61628 100644 --- a/frontend/server/src/components/dbfragments/console.tsx +++ b/frontend/server/src/components/dbfragments/console.tsx @@ -15,21 +15,20 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => { const currentTab: Tab = useContext(TabContext)! - const consoleEndRef = useRef(null) + const consoleRef = useRef(null) + const consoleEndRef = useRef(null) const dbConnection = useAppSelector(selectDBConnection) const output = useAppSelector(selectBlocks) const [input, setInput] = useState("") const [nfocus, setFocus] = useState(0) - const commands = output.filter( e => e.cmd === true) - const [pointer, setPointer] = useState(commands.length-1) - + const history = output.filter(e => e.cmd).filter(e => e.text !== "").map(e => e.text) useEffect(() => { dispatch(initConsole(dbConnection!.id)) }, [dbConnection]) useEffect(() => { - consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + scrollToBottom("smooth") }, [output]) const confirmInput = () => { @@ -38,12 +37,18 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => { } const focus = (e: any) => { - if (e.target.id === "console") { + if (consoleRef.current?.contains(e.target)) { setFocus(Math.random()) } } - return
+ const scrollToBottom = (behavior: ScrollBehavior) => { + const mainContentDiv = consoleRef.current?.parentNode as HTMLDivElement + if (mainContentDiv.scrollTop !== consoleEndRef.current?.offsetTop) + mainContentDiv.scrollTo({ top: consoleEndRef.current?.offsetTop, behavior }) + } + + return
{ {output.map((block, idx) => { return })} - - + +
} @@ -68,9 +73,10 @@ const PromptInputWithRef = (props: any) => { const defaultValue = useRef("") const inputRef = useRef(null) - + const [pointer, setPointer] = useState(-1) useEffect(() => { if (props.isActive) { + props.scrollToBottom("instant") inputRef.current?.focus() } }, [props.isActive, props.nfocus]) @@ -81,8 +87,8 @@ const PromptInputWithRef = (props: any) => { } } - const setInputRef = ( cmd : string) => { - if(inputRef.current !== null){ + const setInputRef = (cmd: string) => { + if (inputRef.current !== null) { inputRef.current.textContent = cmd; } } @@ -92,14 +98,30 @@ const PromptInputWithRef = (props: any) => { if (inputRef.current) { inputRef.current.innerText = "" } + setPointer(-1) } - if ( event.key.toLocaleLowerCase() === 'arrowup') { - props.setPointer( () => ((props.pointer + props.commands.length -1 ) % props.commands.length)) - setInputRef(props.commands.at(props.pointer)?.text) + const updateInputFromPointer = (newPointer: number) => { + let text = props.history.at(props.history.length - 1 - newPointer) + if (!text) { + text = "" + } + setInputRef(text) + } + if (event.key.toLocaleLowerCase() === 'arrowup') { + if (pointer !== props.history.length - 1) { + setPointer(() => (pointer + 1)) + updateInputFromPointer(pointer + 1) + } } - if ( event.key.toLocaleLowerCase() === 'arrowdown'){ - props.setPointer( () => ((props.pointer + 1 ) % props.commands.length)) - setInputRef(props.commands.at(props.pointer)?.text) + if (event.key.toLocaleLowerCase() === 'arrowdown') { + let newPointer + if (pointer < 0) { + newPointer = -1 + } else { + newPointer = pointer - 1 + } + setPointer(newPointer) + updateInputFromPointer(newPointer) } } diff --git a/internal/common/analytics/analytics.go b/internal/common/analytics/analytics.go index efa4cb9f..c0cd9791 100644 --- a/internal/common/analytics/analytics.go +++ b/internal/common/analytics/analytics.go @@ -81,3 +81,7 @@ func SendLowCodeModelViewEvent() { func SendUpdatedTelemetryEvent(value bool) { sendEvent("Updated Telemetry Settings", map[string]interface{}{"value": value}) } + +func SendAISQLGeneratedEvent() { + sendEvent("AI SQL Generated", map[string]interface{}{}) +} diff --git a/internal/desktop/events/ai.go b/internal/desktop/events/ai.go index bbe2b51c..badc1c77 100644 --- a/internal/desktop/events/ai.go +++ b/internal/desktop/events/ai.go @@ -3,6 +3,7 @@ package events import ( "context" + "github.com/slashbaseide/slashbase/internal/common/analytics" "github.com/slashbaseide/slashbase/internal/common/controllers" "github.com/wailsapp/wails/v2/pkg/runtime" ) @@ -21,6 +22,7 @@ func (AIEventListeners) GenSQLEvent(ctx context.Context) { defer recovery(ctx, responseEventName) dbConnectionId := args[1].(string) text := args[2].(string) + analytics.SendAISQLGeneratedEvent() output, err := aiController.GenerateSQL(dbConnectionId, text) if err != nil { runtime.EventsEmit(ctx, responseEventName, map[string]interface{}{ diff --git a/internal/server/handlers/ai.go b/internal/server/handlers/ai.go index 2e266d8f..7f787716 100644 --- a/internal/server/handlers/ai.go +++ b/internal/server/handlers/ai.go @@ -2,6 +2,7 @@ package handlers import ( "github.com/gofiber/fiber/v2" + "github.com/slashbaseide/slashbase/internal/common/analytics" "github.com/slashbaseide/slashbase/internal/common/controllers" ) @@ -20,6 +21,7 @@ func (AIHandlers) GenerateSQL(c *fiber.Ctx) error { "error": err.Error(), }) } + analytics.SendAISQLGeneratedEvent() output, err := aiController.GenerateSQL(body.DBConnectionID, body.Text) if err != nil { return c.JSON(map[string]interface{}{ diff --git a/wails.json b/wails.json index 5b5fe6bc..ec64dadd 100644 --- a/wails.json +++ b/wails.json @@ -13,7 +13,7 @@ }, "info": { "productName": "Slashbase", - "productVersion": "v0.10.0", + "productVersion": "v0.10.1", "copyright": "Copyright © Slashbase.com", "comments": "Open-source Modern database IDE" }