Skip to content
This repository was archived by the owner on Feb 28, 2026. It is now read-only.

Commit 3bba097

Browse files
authored
Merge pull request #134 from slashbaseide/develop
Release v0.10.1
2 parents 94b9820 + de76a11 commit 3bba097

10 files changed

Lines changed: 101 additions & 36 deletions

File tree

frontend/desktop/package.json.md5

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
a24ece3a3d86e13594977247e3352385
1+
371e648509f99cc686955a6284995fb5

frontend/desktop/src/components/dbfragments/console.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@
2222
left: 0;
2323
}
2424

25+
.consoleend {
26+
height: 100px;
27+
}
28+
2529
}

frontend/desktop/src/components/dbfragments/console.tsx

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => {
1515

1616
const currentTab: Tab = useContext(TabContext)!
1717

18-
const consoleEndRef = useRef<HTMLSpanElement>(null)
18+
const consoleRef = useRef<HTMLDivElement>(null)
19+
const consoleEndRef = useRef<HTMLDivElement>(null)
1920

2021
const dbConnection = useAppSelector(selectDBConnection)
2122
const output = useAppSelector(selectBlocks)
2223
const [input, setInput] = useState("")
2324
const [nfocus, setFocus] = useState<number>(0)
24-
const commands = output.filter( e => e.cmd === true)
25-
const [pointer, setPointer] = useState<number>(commands.length-1)
25+
const history = output.filter(e => e.cmd).filter(e => e.text !== "").map(e => e.text)
2626
useEffect(() => {
2727
dispatch(initConsole(dbConnection!.id))
2828
}, [dbConnection])
2929

3030
useEffect(() => {
31-
consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' })
31+
scrollToBottom("smooth")
3232
}, [output])
3333

3434
const confirmInput = () => {
@@ -37,21 +37,27 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => {
3737
}
3838

3939
const focus = (e: any) => {
40-
if (e.target.id === "console") {
40+
if (consoleRef.current?.contains(e.target)) {
4141
setFocus(Math.random())
4242
}
4343
}
4444

45-
return <div className={styles.console + " " + (currentTab.isActive ? "db-tab-active" : "db-tab")} id="console" onClick={focus}>
45+
const scrollToBottom = (behavior: ScrollBehavior) => {
46+
const mainContentDiv = consoleRef.current?.parentNode as HTMLDivElement
47+
if (mainContentDiv.scrollTop !== consoleEndRef.current?.offsetTop)
48+
mainContentDiv.scrollTo({ top: consoleEndRef.current?.offsetTop, behavior })
49+
}
50+
51+
return <div className={styles.console + " " + (currentTab.isActive ? "db-tab-active" : "db-tab")} id="console" ref={consoleRef} onClick={focus}>
4652
<OutputBlock block={{
4753
text: "Start typing command and press enter to run it.\nType 'help' for more info on console.",
4854
cmd: false
4955
}} />
5056
{output.map((block, idx) => {
5157
return <OutputBlock block={block} key={idx} />
5258
})}
53-
<PromptInputWithRef onChange={setInput} isActive={currentTab.isActive} nfocus={nfocus} confirmInput={confirmInput} commands={commands} pointer={pointer} setPointer={setPointer} />
54-
<span ref={consoleEndRef}></span>
59+
<PromptInputWithRef onChange={setInput} isActive={currentTab.isActive} nfocus={nfocus} scrollToBottom={scrollToBottom} confirmInput={confirmInput} history={history} />
60+
<div id="consoleend" className={styles.consoleend} ref={consoleEndRef}></div>
5561
</div>
5662
}
5763

@@ -66,8 +72,10 @@ const OutputBlock = ({ block }: any) => {
6672
const PromptInputWithRef = (props: any) => {
6773
const defaultValue = useRef("")
6874
const inputRef = useRef<HTMLParagraphElement>(null)
75+
const [pointer, setPointer] = useState<number>(-1)
6976
useEffect(() => {
7077
if (props.isActive) {
78+
props.scrollToBottom("instant")
7179
inputRef.current?.focus()
7280
}
7381
}, [props.isActive, props.nfocus])
@@ -78,8 +86,8 @@ const PromptInputWithRef = (props: any) => {
7886
}
7987
}
8088

81-
const setInputRef = ( cmd : string) => {
82-
if(inputRef.current !== null){
89+
const setInputRef = (cmd: string) => {
90+
if (inputRef.current !== null) {
8391
inputRef.current.textContent = cmd;
8492
}
8593
}
@@ -89,14 +97,30 @@ const PromptInputWithRef = (props: any) => {
8997
if (inputRef.current) {
9098
inputRef.current.innerText = ""
9199
}
100+
setPointer(-1)
101+
}
102+
const updateInputFromPointer = (newPointer: number) => {
103+
let text = props.history.at(props.history.length - 1 - newPointer)
104+
if (!text) {
105+
text = ""
106+
}
107+
setInputRef(text)
92108
}
93-
if ( event.key.toLocaleLowerCase() === 'arrowup') {
94-
props.setPointer( () => ((props.pointer + props.commands.length -1 ) % props.commands.length))
95-
setInputRef(props.commands.at(props.pointer)?.text)
109+
if (event.key.toLocaleLowerCase() === 'arrowup') {
110+
if (pointer !== props.history.length - 1) {
111+
setPointer(() => (pointer + 1))
112+
updateInputFromPointer(pointer + 1)
113+
}
96114
}
97-
if ( event.key.toLocaleLowerCase() === 'arrowdown'){
98-
props.setPointer( () => ((props.pointer + 1 ) % props.commands.length))
99-
setInputRef(props.commands.at(props.pointer)?.text)
115+
if (event.key.toLocaleLowerCase() === 'arrowdown') {
116+
let newPointer
117+
if (pointer < 0) {
118+
newPointer = -1
119+
} else {
120+
newPointer = pointer - 1
121+
}
122+
setPointer(newPointer)
123+
updateInputFromPointer(newPointer)
100124
}
101125
}
102126

frontend/desktop/src/redux/consoleSlice.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export const runConsoleCmd = createAsyncThunk(
2222
async (payload: { dbConnId: string, cmdString: string }, { rejectWithValue, getState }: any) => {
2323
const dbConnectionId = payload.dbConnId
2424
const cmdString = payload.cmdString
25+
if (cmdString === "") {
26+
return rejectWithValue("empty command")
27+
}
2528
const result = await eventService.runConsoleCommand(dbConnectionId, cmdString)
2629
if (result.success) {
2730
return {

frontend/server/src/components/dbfragments/console.module.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@
2222
left: 0;
2323
}
2424

25+
.consoleend {
26+
height: 100px;
27+
}
28+
2529
}

frontend/server/src/components/dbfragments/console.tsx

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,20 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => {
1515

1616
const currentTab: Tab = useContext(TabContext)!
1717

18-
const consoleEndRef = useRef<HTMLSpanElement>(null)
18+
const consoleRef = useRef<HTMLDivElement>(null)
19+
const consoleEndRef = useRef<HTMLDivElement>(null)
1920

2021
const dbConnection = useAppSelector(selectDBConnection)
2122
const output = useAppSelector(selectBlocks)
2223
const [input, setInput] = useState("")
2324
const [nfocus, setFocus] = useState<number>(0)
24-
const commands = output.filter( e => e.cmd === true)
25-
const [pointer, setPointer] = useState<number>(commands.length-1)
26-
25+
const history = output.filter(e => e.cmd).filter(e => e.text !== "").map(e => e.text)
2726
useEffect(() => {
2827
dispatch(initConsole(dbConnection!.id))
2928
}, [dbConnection])
3029

3130
useEffect(() => {
32-
consoleEndRef.current?.scrollIntoView({ behavior: 'smooth' })
31+
scrollToBottom("smooth")
3332
}, [output])
3433

3534
const confirmInput = () => {
@@ -38,21 +37,27 @@ const DBConsoleFragment = ({ }: DBConsolePropType) => {
3837
}
3938

4039
const focus = (e: any) => {
41-
if (e.target.id === "console") {
40+
if (consoleRef.current?.contains(e.target)) {
4241
setFocus(Math.random())
4342
}
4443
}
4544

46-
return <div className={styles.console + " " + (currentTab.isActive ? "db-tab-active" : "db-tab")} id="console" onClick={focus}>
45+
const scrollToBottom = (behavior: ScrollBehavior) => {
46+
const mainContentDiv = consoleRef.current?.parentNode as HTMLDivElement
47+
if (mainContentDiv.scrollTop !== consoleEndRef.current?.offsetTop)
48+
mainContentDiv.scrollTo({ top: consoleEndRef.current?.offsetTop, behavior })
49+
}
50+
51+
return <div className={styles.console + " " + (currentTab.isActive ? "db-tab-active" : "db-tab")} id="console" ref={consoleRef} onClick={focus}>
4752
<OutputBlock block={{
4853
text: "Start typing command and press enter to run it.\nType 'help' for more info on console.",
4954
cmd: false
5055
}} />
5156
{output.map((block, idx) => {
5257
return <OutputBlock block={block} key={idx} />
5358
})}
54-
<PromptInputWithRef onChange={setInput} isActive={currentTab.isActive} nfocus={nfocus} confirmInput={confirmInput} commands={commands} pointer={pointer} setPointer={setPointer} />
55-
<span ref={consoleEndRef}></span>
59+
<PromptInputWithRef onChange={setInput} isActive={currentTab.isActive} nfocus={nfocus} scrollToBottom={scrollToBottom} confirmInput={confirmInput} history={history} />
60+
<div id="consoleend" className={styles.consoleend} ref={consoleEndRef}></div>
5661
</div>
5762
}
5863

@@ -68,9 +73,10 @@ const PromptInputWithRef = (props: any) => {
6873

6974
const defaultValue = useRef("")
7075
const inputRef = useRef<HTMLParagraphElement>(null)
71-
76+
const [pointer, setPointer] = useState<number>(-1)
7277
useEffect(() => {
7378
if (props.isActive) {
79+
props.scrollToBottom("instant")
7480
inputRef.current?.focus()
7581
}
7682
}, [props.isActive, props.nfocus])
@@ -81,8 +87,8 @@ const PromptInputWithRef = (props: any) => {
8187
}
8288
}
8389

84-
const setInputRef = ( cmd : string) => {
85-
if(inputRef.current !== null){
90+
const setInputRef = (cmd: string) => {
91+
if (inputRef.current !== null) {
8692
inputRef.current.textContent = cmd;
8793
}
8894
}
@@ -92,14 +98,30 @@ const PromptInputWithRef = (props: any) => {
9298
if (inputRef.current) {
9399
inputRef.current.innerText = ""
94100
}
101+
setPointer(-1)
95102
}
96-
if ( event.key.toLocaleLowerCase() === 'arrowup') {
97-
props.setPointer( () => ((props.pointer + props.commands.length -1 ) % props.commands.length))
98-
setInputRef(props.commands.at(props.pointer)?.text)
103+
const updateInputFromPointer = (newPointer: number) => {
104+
let text = props.history.at(props.history.length - 1 - newPointer)
105+
if (!text) {
106+
text = ""
107+
}
108+
setInputRef(text)
109+
}
110+
if (event.key.toLocaleLowerCase() === 'arrowup') {
111+
if (pointer !== props.history.length - 1) {
112+
setPointer(() => (pointer + 1))
113+
updateInputFromPointer(pointer + 1)
114+
}
99115
}
100-
if ( event.key.toLocaleLowerCase() === 'arrowdown'){
101-
props.setPointer( () => ((props.pointer + 1 ) % props.commands.length))
102-
setInputRef(props.commands.at(props.pointer)?.text)
116+
if (event.key.toLocaleLowerCase() === 'arrowdown') {
117+
let newPointer
118+
if (pointer < 0) {
119+
newPointer = -1
120+
} else {
121+
newPointer = pointer - 1
122+
}
123+
setPointer(newPointer)
124+
updateInputFromPointer(newPointer)
103125
}
104126
}
105127

internal/common/analytics/analytics.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@ func SendLowCodeModelViewEvent() {
8181
func SendUpdatedTelemetryEvent(value bool) {
8282
sendEvent("Updated Telemetry Settings", map[string]interface{}{"value": value})
8383
}
84+
85+
func SendAISQLGeneratedEvent() {
86+
sendEvent("AI SQL Generated", map[string]interface{}{})
87+
}

internal/desktop/events/ai.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package events
33
import (
44
"context"
55

6+
"github.com/slashbaseide/slashbase/internal/common/analytics"
67
"github.com/slashbaseide/slashbase/internal/common/controllers"
78
"github.com/wailsapp/wails/v2/pkg/runtime"
89
)
@@ -21,6 +22,7 @@ func (AIEventListeners) GenSQLEvent(ctx context.Context) {
2122
defer recovery(ctx, responseEventName)
2223
dbConnectionId := args[1].(string)
2324
text := args[2].(string)
25+
analytics.SendAISQLGeneratedEvent()
2426
output, err := aiController.GenerateSQL(dbConnectionId, text)
2527
if err != nil {
2628
runtime.EventsEmit(ctx, responseEventName, map[string]interface{}{

internal/server/handlers/ai.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package handlers
22

33
import (
44
"github.com/gofiber/fiber/v2"
5+
"github.com/slashbaseide/slashbase/internal/common/analytics"
56
"github.com/slashbaseide/slashbase/internal/common/controllers"
67
)
78

@@ -20,6 +21,7 @@ func (AIHandlers) GenerateSQL(c *fiber.Ctx) error {
2021
"error": err.Error(),
2122
})
2223
}
24+
analytics.SendAISQLGeneratedEvent()
2325
output, err := aiController.GenerateSQL(body.DBConnectionID, body.Text)
2426
if err != nil {
2527
return c.JSON(map[string]interface{}{

wails.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
},
1414
"info": {
1515
"productName": "Slashbase",
16-
"productVersion": "v0.10.0",
16+
"productVersion": "v0.10.1",
1717
"copyright": "Copyright © Slashbase.com",
1818
"comments": "Open-source Modern database IDE"
1919
}

0 commit comments

Comments
 (0)